访问令牌或 API 令牌通常用于 API 场景中的身份认证。令牌是在认证过程中通过应用或授权服务器取得的字符串。访问令牌用于确认用户身份,并在签发前取得同意。
令牌可以有多种形式,例如不透明字符串、JSON Web Token(JWT),或采用 XML 结构的 SAML2。详细规范见 RFC 6750:OAuth 2.0 Authorization Framework: Bearer Token Usage。
使用访问令牌认证器
本指南假定应用已经配置安全系统,并创建了用户对象。尚未完成这些工作时,先阅读 Symfony 安全指南。
1)配置访问令牌认证器
使用认证器时必须配置 token_handler。处理器接收请求中的令牌,并返回正确的用户标识。为了取得标识,实现可能需要加载并验证令牌,例如检查撤销状态、过期时间、数字签名等。下面分别给出 YAML 和 PHP 两种等价配置,选择适合项目的一种:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler: App\Security\AccessTokenHandler
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Security\AccessTokenHandler;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => AccessTokenHandler::class,
],
],
],
],
]);
处理器必须实现 AccessTokenHandlerInterface:
// 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());
}
}
认证器用返回的用户标识,通过用户提供器加载用户。
令牌有效性检查不能省略。上例通过 isValid() 检查令牌,例如是否过期;这个方法的具体实现由应用提供。对 JWT 等自包含访问令牌,处理器必须校验数字签名并理解所有声明,尤其是 sub、iat、nbf 和 exp。
2)配置令牌提取器(可选)
应用现在已能处理传入的令牌。令牌提取器负责从请求头、请求体等位置提取令牌。
默认从请求头 Authorization 读取 Bearer 方案,例如 Authorization: Bearer the-token-value。Symfony 按 RFC 6750 提供这些提取方式:
header(默认)- 通过请求头发送令牌,通常是采用 Bearer 方案的 Authorization 头。
query_string- 令牌放在请求查询字符串中,参数通常为 access_token。
request_body- 令牌放在 POST 请求体中,字段通常为 access_token。
URI 传递方式存在安全弱点,包含令牌的 URL 或请求体也很可能进入日志。因此,只有无法通过请求头传递令牌时,才应使用 query_string 或 request_body。
也可以创建实现 AccessTokenExtractorInterface 的自定义提取器。下面的原文示例展示改用内置提取器,或指定自定义服务的写法;同一配置只选择其中一种:
# 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'
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Security\AccessTokenHandler;
use App\Security\CustomTokenExtractor;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => AccessTokenHandler::class,
// use a different built-in extractor
'token_extractors' => 'request_body',
// or provide the service ID of a custom extractor
'token_extractors' => CustomTokenExtractor::class,
],
],
],
],
]);
可以同时设置多个提取器。顺序很重要:列表中第一个先被调用。
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler: App\Security\AccessTokenHandler
token_extractors:
- 'header'
- 'App\Security\CustomTokenExtractor'
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Security\AccessTokenHandler;
use App\Security\CustomTokenExtractor;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => AccessTokenHandler::class,
'token_extractors' => [
'header',
CustomTokenExtractor::class,
],
],
],
],
],
]);
3)发送请求
至此,应用可以用 API 令牌认证传入请求。使用默认请求头提取器时,可以发送这样的请求测试功能:
$ curl -H 'Authorization: Bearer an-accepted-token-value' \
https://localhost:8000/api/some-route
自定义认证成功处理器
默认情况下,请求继续执行,例如运行路由对应的控制器。如果需要自定义成功后的行为,创建实现 AuthenticationSuccessHandlerInterface 的处理器,并把服务 ID 配置到 success_handler:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler: App\Security\AccessTokenHandler
success_handler: App\Security\Authentication\AuthenticationSuccessHandler
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Security\AccessTokenHandler;
use App\Security\Authentication\AuthenticationSuccessHandler;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => AccessTokenHandler::class,
'success_handler' => AuthenticationSuccessHandler::class,
],
],
],
],
]);
如果要自定义默认失败处理,使用 failure_handler,并创建实现 AuthenticationFailureHandlerInterface 的类。
使用 OpenID Connect(OIDC)
OpenID Connect 是 OpenID 技术的第三代,是以 JSON 为数据格式的 RESTful HTTP API。它在 OAuth 2.0 授权框架上增加身份认证层,让应用根据授权服务器完成的认证验证最终用户身份。
1)配置 OidcUserInfoTokenHandler
OidcUserInfoTokenHandler 需要 symfony/http-client 发出 HTTP 请求。尚未安装时运行:
$ composer require symfony/http-client
Symfony 提供通用的 OidcUserInfoTokenHandler,用于调用 OIDC 服务器并取得用户信息:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
oidc_user_info: https://www.example.com/realms/demo/protocol/openid-connect/userinfo
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc_user_info' => 'https://www.example.com/realms/demo/protocol/openid-connect/userinfo',
],
],
],
],
],
]);
启用 OpenID Connect Discovery 时,还需要 symfony/cache,将 OIDC 配置存入缓存。尚未安装时运行:
$ composer require symfony/cache
然后配置 base_uri 和 discovery:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
oidc_user_info:
base_uri: https://www.example.com/realms/demo/
discovery:
cache:
id: cache.app
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc_user_info' => [
'base_uri' => 'https://www.example.com/realms/demo/',
'discovery' => [
'cache' => [
'id' => 'cache.app',
],
],
],
],
],
],
],
],
]);
按照 OpenID Connect 规范,默认以 sub 声明作为用户标识。若需采用其他声明,用 claim 指定:
# 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
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'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 指定服务名:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
oidc_user_info:
client: oidc.client
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc_user_info' => [
'client' => 'oidc.client',
],
],
],
],
],
],
]);
默认情况下,OidcUserInfoTokenHandler 根据声明创建 OidcUser。若需从声明创建自己的用户对象,必须提供自定义 UserProvider:
// 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
}
}
这里的用户提供器是骨架:还需要补齐项目中的 UserInterface 导入,并实际实现加载和返回用户对象的逻辑。后面的 OidcTokenHandler 示例使用同样的骨架。
2)配置 OidcTokenHandler
OidcTokenHandler 需要 web-token/jwt-library。尚未安装时运行:
$ composer require web-token/jwt-library
生产环境应安装 GMP PHP 扩展。web-token/jwt-library 依赖 brick/math;没有 GMP 或 BCMath 时,它会静默退回纯 PHP 实现,这可能使 JWT 验证慢上几个数量级。
Symfony 提供通用的 OidcTokenHandler,负责解码、验证令牌,并从中取得用户信息。令牌也可以采用 JWE 加密:
# 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']
encryption:
enabled: true # Default to false
enforce: false # Default to false, requires an encrypted token when true
algorithms: ['ECDH-ES', 'A128GCM']
keyset: '{"keys": [...]}' # Encryption private keyset
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'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'],
// Encryption:
'encryption' => [
'enabled' => true, // Default to false
'enforce' => false, // Default to false, requires an encrypted token when true
'algorithms' => ['ECDH-ES', 'A128GCM'],
'keyset' => '{"keys": [...]}' // Encryption private keyset
],
],
],
],
],
],
],
]);
启用 OpenID Connect Discovery 时,OidcTokenHandler 也需要 symfony/cache 缓存 OIDC 配置。尚未安装时运行:
$ composer require symfony/cache
随后可以删除 keyset 配置项,由 OpenID Connect Discovery 导入密钥集,并配置 discovery:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
oidc:
claim: email
algorithms: ['ES256', 'RS256']
audience: 'api-example'
issuers: ['https://oidc.example.com']
discovery:
base_uri: https://www.example.com/realms/demo/
cache:
id: cache.app
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc' => [
'claim' => 'email',
'algorithms' => ['ES256', 'RS256'],
'audience' => 'api-example',
'issuers' => ['https://oidc.example.com'],
'discovery' => [
'base_uri' => 'https://www.example.com/realms/demo/',
'cache' => [
'id' => 'cache.app',
],
],
],
],
],
],
],
],
]);
默认使用 Discovery 时,只接受明确指定用于签名验证的密钥:按 RFC 7517,use 为 sig,或 key_ops 包含 sign 或 verify。若身份提供方返回的密钥没有用途标记,即没有 use 或 key_ops 字段,可以将 enforce_key_usage_verification 设为 false,关闭严格用途筛选:
security:
firewalls:
main:
access_token:
token_handler:
oidc:
# ...
discovery:
base_uri: https://www.example.com/realms/demo/
cache:
id: cache.app
enforce_key_usage_verification: false
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc' => [
// ...
'discovery' => [
'base_uri' => 'https://www.example.com/realms/demo/',
'cache' => [
'id' => 'cache.app',
],
'enforce_key_usage_verification' => false,
],
],
],
],
],
],
],
]);
关闭之后仍会筛选密钥:明确只用于加密的密钥(use: enc,或 key_ops 只有加密操作)仍被排除;没有用途标记的密钥则会被纳入。enforce_key_usage_verification 在 Symfony 8.1 引入。
默认仍按 OpenID Connect 规范使用 sub 作为用户标识。需要其他声明时,在配置中指定:
# 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']
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'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:
// 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
}
}
配置多个 OIDC Discovery 端点
OidcTokenHandler 支持多个 Discovery 端点,可以验证来自不同身份提供方的令牌:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
oidc:
algorithms: ['ES256', 'RS256']
audience: 'api-example'
# each "issuer" announced by the discovery documents
issuers:
- https://idp1.example.com/realms/demo
- https://idp2.example.com/realms/demo
discovery:
base_uri:
- https://idp1.example.com/realms/demo/
- https://idp2.example.com/realms/demo/
cache:
id: cache.app
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'oidc' => [
'algorithms' => ['ES256', 'RS256'],
'audience' => 'api-example',
// each "issuer" announced by the discovery documents
'issuers' => [
'https://idp1.example.com/realms/demo',
'https://idp2.example.com/realms/demo',
],
'discovery' => [
'base_uri' => [
'https://idp1.example.com/realms/demo/',
'https://idp2.example.com/realms/demo/',
],
'cache' => [
'id' => 'cache.app',
],
],
],
],
],
],
],
],
]);
处理器取得各 Discovery 端点的 JWK 集,并将每组密钥绑定到该 Discovery 文档宣布的签发者。因此,每个令牌只能用自己的 iss 对应签发者的密钥验证。issuers 必须逐字列出所有宣布的签发者,包括末尾斜杠;两个 Discovery 文档不能宣布同一签发者。
在命令行创建 OIDC 令牌
security:oidc:generate-token 帮助生成 JWT,主要用于开发或测试采用 OIDC 认证的应用:
# generate a token using the default configuration
$ php bin/console security:oidc:generate-token john.doe@example.com
# specify the firewall, algorithm, and issuer if multiple are available
$ php bin/console security:oidc:generate-token john.doe@example.com \
--firewall="api" \
--algorithm="HS256" \
--issuer="https://example.com"
用于签名的 JWK 必须设置正确的密钥操作标志。
使用 CAS 2.0
Central Authentication Service(CAS)是面向 Web 的企业多语言单点登录方案和身份提供方,旨在提供完整的认证与授权平台。
配置 Cas2Handler
Symfony 提供通用的 Cas2Handler,用来调用 CAS 服务器。它需要 symfony/http-client 发出 HTTP 请求;尚未安装时运行:
$ composer require symfony/http-client
可以如下配置 CAS 令牌处理器:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
cas:
validation_url: https://www.example.com/cas/validate
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'cas' => [
'validation_url' => 'https://www.example.com/cas/validate',
],
],
],
],
],
],
]);
处理器自动创建 HTTP 客户端来访问指定的 validation_url。若要用自己的客户端,通过 http_client 指定服务名:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
cas:
validation_url: https://www.example.com/cas/validate
http_client: cas.client
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'cas' => [
'validation_url' => 'https://www.example.com/cas/validate',
'http_client' => 'cas.client',
],
],
],
],
],
],
]);
默认使用 cas 前缀读取验证 URL 返回的 XML 响应,也可以配置其他前缀:
# config/packages/security.yaml
security:
firewalls:
main:
access_token:
token_handler:
cas:
validation_url: https://www.example.com/cas/validate
prefix: cas-example
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return App::config([
'security' => [
'firewalls' => [
'main' => [
'access_token' => [
'token_handler' => [
'cas' => [
'validation_url' => 'https://www.example.com/cas/validate',
'prefix' => 'cas-example',
],
],
],
],
],
],
]);
从令牌创建用户
某些令牌(例如 OIDC)包含创建用户实体所需的全部信息,例如用户名和角色。这时不必使用从数据库加载用户的提供器:
// 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 配置。
来源:Symfony 文档:How to use Access Token Authentication,Symfony 文档贡献者。2026-10-03 核对 Symfony 8.1 当前文档。本中文版本完整翻译正文,并按顺序展开原文的 YAML/PHP 配置选项;代码原样保留,补充了二选一提示。全部内容(包括示例代码)遵循 Creative Commons Attribution-ShareAlike 3.0 Unported;改编版本也按同一许可提供。许可依据见 官方许可说明。示例包括应用自行实现的仓库、用户加载器和占位密钥,尚未在本机运行 Symfony 应用或 OIDC/CAS 服务。











暂无评论内容