跳到内容

如何使用访问令牌认证

编辑此页

访问令牌或 API 令牌通常用作 API 上下文中的身份验证机制。访问令牌是一个字符串,在身份验证期间(使用应用程序或授权服务器)获得。访问令牌的作用是在颁发令牌之前验证用户身份并获得同意。

访问令牌可以是任何类型,例如不透明字符串、JSON Web Tokens (JWT)SAML2(XML 结构)。有关详细规范,请参阅 RFC6750OAuth 2.0 授权框架:Bearer 令牌用法

使用访问令牌认证器

本指南假定您已设置安全性并在您的应用程序中创建了用户对象。如果尚未完成,请遵循主安全指南

1) 配置访问令牌认证器

要使用访问令牌认证器,您必须配置一个 token_handler。令牌处理程序从请求中接收令牌并返回正确的用户标识符。为了获取用户标识符,实现可能需要加载和验证令牌(例如,撤销、过期时间、数字签名等)。

1
2
3
4
5
6
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler

此处理程序必须实现 AccessTokenHandlerInterface

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
// src/Security/AccessTokenHandler.php
namespace App\Security;

use App\Repository\AccessTokenRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    public function __construct(
        private AccessTokenRepository $repository
    ) {
    }

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // e.g. query the "access token" database to search for this token
        $accessToken = $this->repository->findOneByValue($accessToken);
        if (null === $accessToken || !$accessToken->isValid()) {
            throw new BadCredentialsException('Invalid credentials.');
        }

        // and return a UserBadge object containing the user identifier from the found token
        // (this is the same identifier used in Security configuration; it can be an email,
        // a UUID, a username, a database ID, etc.)
        return new UserBadge($accessToken->getUserId());
    }
}

访问令牌认证器将使用返回的用户标识符,通过 用户提供器 加载用户。

警告

检查令牌是否有效非常重要。例如,上面的示例验证令牌是否已过期。对于自包含访问令牌(如 JWT),处理程序需要验证数字签名并理解所有声明,尤其是 subiatnbfexp

2) 配置令牌提取器 (可选)

应用程序现在可以处理传入的令牌。令牌提取器从请求中检索令牌(例如,标头或请求正文)。

默认情况下,访问令牌从请求头参数 Authorization 中读取,方案为 Bearer(例如,Authorization: Bearer the-token-value)。

Symfony 按照 RFC6750 提供了其他提取器

header(默认)
令牌通过请求头发送。通常是带有 Bearer 方案的 Authorization
query_string
令牌是请求查询字符串的一部分。通常是 access_token
request_body
令牌是 POST 请求期间请求正文的一部分。通常是 access_token

警告

由于与 URI 方法相关的安全弱点,包括 URL 或包含访问令牌的请求正文很可能被记录,因此 不应 使用 query_stringrequest_body 方法,除非无法在请求头字段中传输访问令牌。

您还可以创建自定义提取器。该类必须实现 AccessTokenExtractorInterface

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler

                # use a different built-in extractor
                token_extractors: request_body

                # or provide the service ID of a custom extractor
                token_extractors: 'App\Security\CustomTokenExtractor'

可以设置多个提取器。在这种情况下,顺序很重要:列表中的第一个先被调用。

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler
                token_extractors:
                    - 'header'
                    - 'App\Security\CustomTokenExtractor'

3) 提交请求

就这样!你的应用程序现在可以使用 API 令牌验证传入的请求。

使用默认的头提取器,你可以通过提交如下请求来测试该功能

1
2
$ curl -H 'Authorization: Bearer an-accepted-token-value' \
    https://127.0.0.1:8000/api/some-route

自定义成功处理器

默认情况下,请求继续(例如,路由的控制器运行)。如果你想自定义成功处理,通过创建一个实现 AuthenticationSuccessHandlerInterface 的类,并将服务 ID 配置为 success_handler 来创建你自己的成功处理程序

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler: App\Security\AccessTokenHandler
                success_handler: App\Security\Authentication\AuthenticationSuccessHandler

提示

如果你想自定义默认的失败处理,请使用 failure_handler 选项,并创建一个实现 AuthenticationFailureHandlerInterface 的类。

使用 OpenID Connect (OIDC)

OpenID Connect (OIDC) 是第三代 OpenID 技术,它是一个 RESTful HTTP API,使用 JSON 作为其数据格式。OpenID Connect 是 OAuth 2.0 授权框架之上的身份验证层。它允许基于授权服务器执行的身份验证来验证最终用户的身份。

1) 配置 OidcUserInfoTokenHandler

OidcUserInfoTokenHandler 需要 symfony/http-client 包来发出所需的 HTTP 请求。如果你尚未安装它,请运行此命令

1
$ composer require symfony/http-client

Symfony 提供了一个通用的 OidcUserInfoTokenHandler 来调用你的 OIDC 服务器并检索用户信息

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info: https://www.example.com/realms/demo/protocol/openid-connect/userinfo

按照 OpenID Connect 规范,默认情况下 sub 声明用作用户标识符。要使用另一个声明,请在配置中指定它

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info:
                        claim: email
                        base_uri: https://www.example.com/realms/demo/protocol/openid-connect/userinfo

oidc_user_info 令牌处理程序会自动创建一个带有指定 base_uri 的 HTTP 客户端。如果你更喜欢使用自己的客户端,可以通过 client 选项指定服务名称

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc_user_info:
                        client: oidc.client

默认情况下,OidcUserInfoTokenHandler 使用声明创建一个 OidcUser。要从声明中创建你自己的用户对象,你必须创建你自己的 UserProvider

1
2
3
4
5
6
7
8
9
10
// src/Security/Core/User/OidcUserProvider.php
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;

class OidcUserProvider implements AttributesBasedUserProviderInterface
{
    public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
    {
        // implement your own logic to load and return the user object
    }
}

2) 配置 OidcTokenHandler

OidcTokenHandler 需要 web-token/jwt-library 包。如果你尚未安装它,请运行此命令

1
$ composer require web-token/jwt-library

Symfony 提供了一个通用的 OidcTokenHandler 来解码你的令牌、验证它并从中检索用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc:
                        # Algorithms used to sign the JWS
                        algorithms: ['ES256', 'RS256']
                        # A JSON-encoded JWK
                        keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                        # Audience (`aud` claim): required for validation purpose
                        audience: 'api-example'
                        # Issuers (`iss` claim): required for validation purpose
                        issuers: ['https://oidc.example.com']

7.1

对用于签署 JWS 的多种算法的支持在 Symfony 7.1 中引入。在以前的版本中,仅支持 ES256 算法。

按照 OpenID Connect 规范,默认情况下 sub 声明用作用户标识符。要使用另一个声明,请在配置中指定它

1
2
3
4
5
6
7
8
9
10
11
12
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    oidc:
                        claim: email
                        algorithms: ['ES256', 'RS256']
                        keyset: '{"keys":[{"kty":"...","k":"..."}]}'
                        audience: 'api-example'
                        issuers: ['https://oidc.example.com']

默认情况下,OidcTokenHandler 使用声明创建一个 OidcUser。要从声明中创建你自己的用户,你必须创建你自己的 UserProvider

1
2
3
4
5
6
7
8
9
10
// src/Security/Core/User/OidcUserProvider.php
use Symfony\Component\Security\Core\User\AttributesBasedUserProviderInterface;

class OidcUserProvider implements AttributesBasedUserProviderInterface
{
    public function loadUserByIdentifier(string $identifier, array $attributes = []): UserInterface
    {
        // implement your own logic to load and return the user object
    }
}

使用 CAS 2.0

7.1

对 CAS 令牌处理程序的支持在 Symfony 7.1 中引入。

中央身份验证服务 (CAS) 是一种企业多语言单点登录解决方案和 Web 身份提供商,旨在成为满足您的身份验证和授权需求的综合平台。

配置 Cas2Handler

Symfony 提供了一个通用的 Cas2Handler 来调用你的 CAS 服务器。它需要 symfony/http-client 包来发出所需的 HTTP 请求。如果你尚未安装它,请运行此命令

1
$ composer require symfony/http-client

你可以按如下方式配置 cas 令牌处理程序

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate

cas 令牌处理程序会自动创建一个 HTTP 客户端来调用指定的 validation_url。如果你更喜欢使用自己的客户端,可以通过 http_client 选项指定服务名称

1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate
                        http_client: cas.client
默认情况下,令牌处理程序将使用 cas 前缀读取验证 URL XML 响应,但你可以配置另一个前缀
cas 前缀,但你可以配置另一个前缀
1
2
3
4
5
6
7
8
9
# config/packages/security.yaml
security:
    firewalls:
        main:
            access_token:
                token_handler:
                    cas:
                        validation_url: https://www.example.com/cas/validate
                        prefix: cas-example

从令牌创建用户

某些类型的令牌(例如 OIDC)包含创建用户实体所需的所有信息(例如,用户名和角色)。在这种情况下,你不需要用户提供器来从数据库创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Security/AccessTokenHandler.php
namespace App\Security;

// ...
class AccessTokenHandler implements AccessTokenHandlerInterface
{
    // ...

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        // get the data from the token
        $payload = ...;

        return new UserBadge(
            $payload->getUserId(),
            fn (string $userIdentifier) => new User($userIdentifier, $payload->getRoles())
        );
    }
}

当使用此策略时,你可以为 无状态防火墙 省略 user_provider 配置。

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