跳到内容

数据定制与验证

编辑此页

注意: 在你添加自己的自定义数据之前,请注意 JWT payload 未加密,仅经过 base64 编码。 令牌签名确保其完整性(意味着它不能被修改),但任何人都可以读取其内容(使用像 https://jwt.node.org.cn/ 这样的简单工具尝试一下)。

向 JWT 添加自定义数据或标头

使用 Events::JWT_CREATED

默认情况下,JWT payload 将包含用户名、用户角色、令牌创建日期和过期日期,但你可以添加自己的数据。

你也可以修改标头以适应你的应用程序上下文。

1
2
3
4
5
6
7
# config/services.yaml
services:
    acme_api.event.jwt_created_listener:
        class: App\EventListener\JWTCreatedListener
        arguments: [ '@request_stack' ]
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated }

示例:向编码后的 payload 添加客户端 IP 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/App/EventListener/JWTCreatedListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * @var RequestStack
 */
private $requestStack;

/**
 * @param RequestStack $requestStack
 */
public function __construct(RequestStack $requestStack)
{
    $this->requestStack = $requestStack;
}

/**
 * @param JWTCreatedEvent $event
 *
 * @return void
 */
public function onJWTCreated(JWTCreatedEvent $event)
{
    $request = $this->requestStack->getCurrentRequest();

    $payload       = $event->getData();
    $payload['ip'] = $request->getClientIp();

    $event->setData($payload);

    $header        = $event->getHeader();
    $header['cty'] = 'JWT';

    $event->setHeader($header);
}

示例:覆盖令牌过期日期计算以使其更灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/App/EventListener/JWTCreatedListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;

/**
 * @param JWTCreatedEvent $event
 *
 * @return void
 */
public function onJWTCreated(JWTCreatedEvent $event)
{
    $expiration = new \DateTime('+1 day');
    $expiration->setTime(2, 0, 0);

    $payload        = $event->getData();
    $payload['exp'] = $expiration->getTimestamp();

    $event->setData($payload);
}

在 JWT 创建时使用自定义 payload

如果你以编程方式创建 JWT 令牌,你可以使用 createFromPayload(UserInterface $user, array $payload) 方法向 JWT 添加自定义数据

1
2
3
$payload = ['foo' => 'bar'];

$jwt = $this->container->get('lexik_jwt_authentication.jwt_manager')->createFromPayload($user, $payload);

Events::JWT_DECODED - 验证 JWT payload 中的数据

你可以访问已解码的 jwt payload,以执行你自己的额外验证。

1
2
3
4
5
6
7
# config/services.yaml
services:
    acme_api.event.jwt_decoded_listener:
        class: App\EventListener\JWTDecodedListener
        arguments: [ '@request_stack' ]
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded }

示例:检查已解码 payload 中的客户端 IP (来自示例 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/App/EventListener/JWTDecodedListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;

/**
 * @param JWTDecodedEvent $event
 *
 * @return void
 */
public function onJWTDecoded(JWTDecodedEvent $event)
{
    $request = $this->requestStack->getCurrentRequest();

    $payload = $event->getPayload();

    if (!isset($payload['ip']) || $payload['ip'] !== $request->getClientIp()) {
        $event->markAsInvalid();
    }
}

示例:向 payload 添加额外数据 - 以便在你的 自定义 UserProvider 中获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/App/EventListener/JWTDecodedListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;

/**
 * @param JWTDecodedEvent $event
 *
 * @return void
 */
public function onJWTDecoded(JWTDecodedEvent $event)
{
    $payload = $event->getPayload();
    $user = $this->userRepository->findOneByUsername($payload['username']);

    $payload['custom_user_data'] = $user->getCustomUserInformations();

    $event->setPayload($payload); // Don't forget to regive the payload for next event / step
}

Events::JWT_AUTHENTICATED - 自定义你的安全令牌

你可以在令牌通过身份验证后向令牌添加属性,以允许你的应用程序使用 JWT 属性。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.jwt_authenticated_listener:
        class: App\EventListener\JWTAuthenticatedListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_authenticated, method: onJWTAuthenticated }

示例:在已验证的令牌中保留 JWT 中设置的 UUID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/App/EventListener/JWTAuthenticatedListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;

/**
 * @param JWTAuthenticatedEvent $event
 *
 * @return void
 */
public function onJWTAuthenticated(JWTAuthenticatedEvent $event)
{
    $token = $event->getToken();
    $payload = $event->getPayload();

    $token->setAttribute('uuid', $payload['uuid']);
}

Events::AUTHENTICATION_SUCCESS - 向 JWT 响应添加公共数据

默认情况下,身份验证响应只是一个包含 JWT 的 json,但你可以向其中添加你自己的公共数据。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.authentication_success_listener:
        class: App\EventListener\AuthenticationSuccessListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }

示例:向响应体添加用户角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/App/EventListener/AuthenticationSuccessListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;

/**
 * @param AuthenticationSuccessEvent $event
 */
public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
{
    $data = $event->getData();
    $user = $event->getUser();

    if (!$user instanceof UserInterface) {
        return;
    }

    $data['data'] = array(
        'roles' => $user->getRoles(),
    );

    $event->setData($data);
}

Events::JWT_ENCODED - 获取编码后的 JWT 令牌字符串

你可能需要在 JWT 创建后获取它。

示例:获取 JWT 字符串

1
2
3
4
5
6
7
8
9
10
// src/App/EventListener/JWTEncodedListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTEncodedEvent;

/**
 * @param JWTEncodedEvent $event
 */
public function onJwtEncoded(JWTEncodedEvent $event)
{
    $token = $event->getJWTString();
}

Events::AUTHENTICATION_FAILURE - 自定义失败响应体

默认情况下,身份验证失败时的响应只是一个包含失败消息和 401 状态码的 json,但你可以设置自定义响应。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.authentication_failure_listener:
        class: App\EventListener\AuthenticationFailureListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_failure, method: onAuthenticationFailureResponse }

示例:设置身份验证失败时的自定义响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/App/EventListener/AuthenticationFailureListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
 * @param AuthenticationFailureEvent $event
 */
public function onAuthenticationFailureResponse(AuthenticationFailureEvent $event)
{
    $data = [
        'name' => 'John Doe',
        'foo'  => 'bar',
    ];

    $response = new JWTAuthenticationFailureResponse('Bad credentials, please verify that your username/password are correctly set', JsonResponse::HTTP_UNAUTHORIZED);
    $response->setData($data);

    $event->setResponse($response);
}

Events::JWT_INVALID - 自定义无效令牌响应

默认情况下,如果令牌无效,则响应只是一个包含相应错误消息和 401 状态码的 json,但你可以设置自定义响应。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.jwt_invalid_listener:
        class: App\EventListener\JWTInvalidListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_invalid, method: onJWTInvalid }

示例:在无效令牌上设置自定义响应消息和状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/App/EventListener/JWTInvalidListener.php
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;

/**
 * @param JWTInvalidEvent $event
 */
public function onJWTInvalid(JWTInvalidEvent $event)
{
    $response = new JWTAuthenticationFailureResponse('Your token is invalid, please login again to get a new one', 403);

    $event->setResponse($response);
}

Events::JWT_NOT_FOUND - 自定义未找到令牌的响应

默认情况下,如果在请求中未找到令牌,身份验证监听器将调用返回未经授权 (401) json 响应的入口点,或者(如果防火墙允许匿名请求),则让请求继续。

感谢此事件,你可以设置自定义响应。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.jwt_notfound_listener:
        class: App\EventListener\JWTNotFoundListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_not_found, method: onJWTNotFound }

示例:设置未找到令牌的自定义响应消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/App/EventListener/JWTNotFoundListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
 * @param JWTNotFoundEvent $event
 */
public function onJWTNotFound(JWTNotFoundEvent $event)
{
    $data = [
        'status'  => '403 Forbidden',
        'message' => 'Missing token',
    ];

    $response = new JsonResponse($data, 403);

    $event->setResponse($response);
}

Events::JWT_EXPIRED - 自定义过期令牌的响应消息

默认情况下,如果请求中提供的令牌已过期,身份验证监听器将调用入口点,返回未经授权 (401) json 响应。 感谢此事件,你可以设置自定义响应或仅更改响应消息。

1
2
3
4
5
6
# config/services.yaml
services:
    acme_api.event.jwt_expired_listener:
        class: App\EventListener\JWTExpiredListener
        tags:
            - { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_expired, method: onJWTExpired }

示例:自定义过期令牌情况下的响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/App/EventListener/JWTExpiredListener.php

use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;

/**
 * @param JWTExpiredEvent $event
 */
public function onJWTExpired(JWTExpiredEvent $event)
{
    /** @var JWTAuthenticationFailureResponse */
    $response = $event->getResponse();

    $response->setMessage('Your token is expired, please renew it.');
}

小贴士: 你可能希望使用相同的方法来自定义 JWT_INVALIDJWT_NOT_FOUND 和/或 JWT_EXPIRED 事件的响应。 为此,请使用 Lexik\Bundle\JWTAuthenticationBundle\Event\JWTFailureEventInterface 接口来类型提示你的监听器方法的事件参数,而不是对应于这些特定事件之一的具体类。

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本