身份验证器

身份验证器负责将请求数据转换为身份验证操作。它们利用 标识符 来查找已知的 身份对象.

会话

此身份验证器将检查会话中是否包含用户数据或凭据。使用任何有状态身份验证器(如下面列出的 Form)时,请确保首先加载 Session 身份验证器,以便在后续请求中,一旦登录,用户数据会从会话本身获取。

配置选项

  • sessionKey: 用户数据的会话密钥,默认值为 Auth

  • identify: 使用布尔值 true 设置此键以启用根据标识符检查会话凭据。当为 true 时,配置的 标识符 用于在每次请求时使用存储在会话中的数据来识别用户。默认值为 false.

  • fields: 允许您将 username 字段映射到用户存储中的唯一标识符。默认为 username。当 identify 选项设置为 true 时,使用此选项。

表单

在请求主体中查找数据,通常是在通过 POST / PUT 提交表单时。

配置选项

  • loginUrl: 登录 URL,字符串或 URL 数组。默认为 null,所有页面都将被检查。

  • fields: 数组,将 usernamepassword 映射到指定的 POST 数据字段。

  • urlChecker: URL 检查器类或对象。默认为 DefaultUrlChecker.

  • useRegex: 是否使用正则表达式进行 URL 匹配。默认为 false.

  • checkFullUrl: 是否检查完整 URL,包括查询字符串。在登录表单位于不同子域的情况下很有用。默认为 false。当在查询字符串中保留未经身份验证的重定向时,此选项效果不佳。

如果您正在构建 API 并希望通过 JSON 请求接受凭据,请确保您已将 BodyParserMiddleware 应用于AuthenticationMiddleware 之前

警告

如果您对 URL 使用数组语法,URL 将由 CakePHP 路由器生成。根据您的路由处理方式,结果可能与您在请求 URI 中实际拥有的内容不同。因此,请将此视为区分大小写的!

令牌

令牌身份验证器可以根据与请求一起发送的位于标头或请求参数中的令牌来验证请求。这要求用户表中有一个令牌列,以便可以在接收到的令牌和存储的令牌之间进行比较。

配置选项

  • queryParam: 查询参数的名称。如果您想从查询参数中获取令牌,请对其进行配置。

  • header: 标头的名称。如果您想从标头中获取令牌,请对其进行配置。

  • tokenPrefix: 可选令牌前缀。

从标头或查询字符串获取令牌的示例如下所示

$service->loadAuthenticator('Authentication.Token', [
    'queryParam' => 'token',
    'header' => 'Authorization',
    'tokenPrefix' => 'Token'
]);

以上代码将读取 token GET 参数或 Authorization 标头,只要令牌以 Token 和空格开头。

令牌始终会以以下方式传递给配置的标识符

[
    'token' => '{token-value}',
]

JWT

JWT 身份验证器从标头或查询参数获取 JWT 令牌,然后直接返回有效负载,或将其传递给标识符,以根据其他数据源(例如)进行验证。

  • header: 要检查令牌的标头行。默认为 Authorization.

  • queryParam: 要检查令牌的查询参数。默认为 token.

  • tokenPrefix: 令牌前缀。默认为 bearer.

  • algorithm: Firebase JWT 的散列算法。默认为 'HS256'.

  • returnPayload: 是否直接返回令牌有效负载,而不通过标识符。默认为 true.

  • secretKey: 默认为 null,但如果您不在提供通过 Security::salt() 提供的密钥的 CakePHP 应用程序上下文中,则必须传递一个密钥。

  • jwks: 默认为 null。具有 'keys' 键的关联数组。如果提供,将用于代替密钥。

您需要将 lib firebase/php-jwt v6.2 或更高版本添加到您的应用程序中,才能使用 JwtAuthenticator

默认情况下,JwtAuthenticator 使用 HS256 对称密钥算法,并使用 Cake\Utility\Security::salt() 的值作为加密密钥。为了提高安全性,可以使用 RS256 非对称密钥算法。您可以按如下方式生成所需的密钥:

# generate private key
openssl genrsa -out config/jwt.key 1024
# generate public key
openssl rsa -in config/jwt.key -outform PEM -pubout -out config/jwt.pem

jwt.key 文件是私钥,应该妥善保管。 jwt.pem 文件是公钥。当您需要验证由外部应用程序(例如移动应用程序)创建的令牌时,应使用此文件。

以下示例允许您使用 JwtSubject 标识符根据令牌的 sub(主体)来识别用户,并配置 Authenticator 以使用公钥进行令牌验证。

将以下内容添加到您的 Application 类中

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();
    // ...
    $service->loadIdentifier('Authentication.JwtSubject');
    $service->loadAuthenticator('Authentication.Jwt', [
        'secretKey' => file_get_contents(CONFIG . '/jwt.pem'),
        'algorithm' => 'RS256',
        'returnPayload' => false
    ]);
}

在您的 UsersController

use Firebase\JWT\JWT;

public function login()
{
    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        $privateKey = file_get_contents(CONFIG . '/jwt.key');
        $user = $result->getData();
        $payload = [
            'iss' => 'myapp',
            'sub' => $user->id,
            'exp' => time() + 60,
        ];
        $json = [
            'token' => JWT::encode($payload, $privateKey, 'RS256'),
        ];
    } else {
        $this->response = $this->response->withStatus(401);
        $json = [];
    }
    $this->set(compact('json'));
    $this->viewBuilder()->setOption('serialize', 'json');
}

也支持使用从外部 JWKS 端点获取的 JWKS

// Application.php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();
    // ...
    $service->loadIdentifier('Authentication.JwtSubject');

    $jwksUrl = 'https://appleid.apple.com/auth/keys';

    // Set of keys. The "keys" key is required. Additionally keys require a "alg" key.
    // Add it manually to your JWK array if it doesn't already exist.
    $jsonWebKeySet = Cache::remember('jwks-' . md5($jwksUrl), function () use ($jwksUrl) {
        $http = new Client();
        $response = $http->get($jwksUrl);
        return $response->getJson();
    });

    $service->loadAuthenticator('Authentication.Jwt', [
        'jwks' => $jsonWebKeySet,
        'returnPayload' => false
    ]);
}

JWKS 资源通常会返回同一组密钥。应用程序应缓存这些资源,但它们也需要做好处理签名密钥轮换的准备。

警告

应用程序需要选择一个缓存生存期,以平衡性能和安全性。这在私钥遭到泄露的情况下尤其重要。

除了与外部应用程序共享公钥文件外,您还可以通过配置应用程序如下方式,通过 JWKS 端点分发公钥文件

// config/routes.php
$builder->setExtensions('json');
$builder->connect('/.well-known/:controller/*', [
    'action' => 'index',
], [
    'controller' => '(jwks)',
]); // connect /.well-known/jwks.json to JwksController

// controller/JwksController.php
public function index()
{
    $pubKey = file_get_contents(CONFIG . './jwt.pem');
    $res = openssl_pkey_get_public($pubKey);
    $detail = openssl_pkey_get_details($res);
    $key = [
        'kty' => 'RSA',
        'alg' => 'RS256',
        'use' => 'sig',
        'e' => JWT::urlsafeB64Encode($detail['rsa']['e']),
        'n' => JWT::urlsafeB64Encode($detail['rsa']['n']),
    ];
    $keys['keys'][] = $key;

    $this->viewBuilder()->setClassName('Json');
    $this->set(compact('keys'));
    $this->viewBuilder()->setOption('serialize', 'keys');
}

有关 JWKS 的更多信息,请参阅 https://datatracker.ietf.org/doc/html/rfc7517https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets.

HttpBasic

请参阅 https://en.wikipedia.org/wiki/Basic_access_authentication

注意

此身份验证器将在缺少或无效的验证凭据时停止请求。

配置选项

  • realm: 默认为 $_SERVER['SERVER_NAME'],根据需要进行覆盖。

HttpDigest

请参阅 https://en.wikipedia.org/wiki/Digest_access_authentication

注意

此身份验证器将在缺少或无效的验证凭据时停止请求。

配置选项

  • realm: 默认为 null

  • qop: 默认为 auth

  • nonce: 默认为 uniqid(''),

  • opaque: 默认为 null

环境变量

EnvironmentAuthenticator 可以根据 Web 服务器公开的映射的环境变量来验证用户。这允许通过 Shibboleth 和类似的 SAML 1.1 实现进行身份验证。一个示例配置如下

// Configure a token identifier that maps `USER_ID` to the
// username column
$service->loadIdentifier('Authentication.Token', [
    'tokenField' => 'username',
    'dataField' => 'USER_NAME',
]);

$service->loadAuthenticator('Authentication.Environment', [
    'loginUrl' => '/sso',
    'fields' => [
        // Choose which environment variables exposed by your
        // authentication provider are used to authenticate
        // in your application.
        'USER_NAME',
    ],
]);

版本 2.10.0 中新增: EnvironmentAuthenticator 已添加。

事件

身份验证只触发一个事件:Authentication.afterIdentify

如果您不知道事件是什么以及如何使用它们,请查看文档

Authentication.afterIdentify 事件由 AuthenticationComponent 在成功识别身份后触发。

该事件包含以下数据

  • provider: 实现 \Authentication\Authenticator\AuthenticatorInterface 的对象

  • identity: 实现 \ArrayAccess 的对象

  • service: 实现 \Authentication\AuthenticationServiceInterface 的对象

事件的主题将是附加了 AuthenticationComponent 的当前控制器实例。

但该事件只有在用于识别身份的验证器不是持久的也不是无状态的情况下才会触发。这样做的原因是,该事件将每次都会触发,因为会话验证器或令牌(例如)将每次都会在每个请求中触发它。

在包含的验证器中,只有 FormAuthenticator 会导致事件触发。之后,会话验证器将提供身份。

URL 检查器

一些验证器,例如 FormCookie,应该只在某些页面上执行,例如 /login 页面。这可以通过使用 URL 检查器来实现。

默认情况下,使用 DefaultUrlChecker,它使用字符串 URL 与支持正则表达式检查进行比较。

配置选项

  • useRegex: 是否使用正则表达式进行 URL 匹配。默认为 false.

  • checkFullUrl: 是否检查完整 URL。当登录表单位于不同的子域时很有用。默认值为 false

如果需要对框架特定 URL 的支持,则可以实现自定义 URL 检查器。在这种情况下,应该实现 Authentication\UrlChecker\UrlCheckerInterface

有关 URL 检查器的更多详细信息,请参见此文档页面

获取成功的验证器或标识符

在用户经过身份验证后,您可能想要检查或与成功验证用户的验证器进行交互

// In a controller action
$service = $this->request->getAttribute('authentication');

// Will be null on authentication failure, or an authenticator.
$authenticator = $service->getAuthenticationProvider();

您还可以获取识别用户的标识符

// In a controller action
$service = $this->request->getAttribute('authentication');

// Will be null on authentication failure, or an identifier.
$identifier = $service->getIdentificationProvider();

将无状态验证器与有状态验证器一起使用

当使用 HttpBasicHttpDigest 与其他验证器时,您应该记住,当缺少或无效身份验证凭据时,这些验证器将停止请求。这是必要的,因为这些验证器必须在响应中发送特定的挑战标头

use Authentication\AuthenticationService;

// Instantiate the service
$service = new AuthenticationService();

// Load identifiers
$service->loadIdentifier('Authentication.Password', [
    'fields' => [
        'username' => 'email',
        'password' => 'password'
    ]
]);
$service->loadIdentifier('Authentication.Token');

// Load the authenticators leaving Basic as the last one.
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form');
$service->loadAuthenticator('Authentication.HttpBasic');

如果您想将 HttpBasicHttpDigest 与其他验证器结合使用,请注意,这些验证器将中止请求并强制浏览器对话框。

处理未经身份验证的错误

当用户未经身份验证时,AuthenticationComponent 将引发异常。您可以使用 unauthenticatedRedirect 将此异常转换为重定向,方法是在配置 AuthenticationService 时使用它。

您还可以使用 queryParam 选项将当前请求目标 URI 作为查询参数传递

// In the getAuthenticationService() method of your src/Application.php

$service = new AuthenticationService();

// Configure unauthenticated redirect
$service->setConfig([
    'unauthenticatedRedirect' => '/users/login',
    'queryParam' => 'redirect',
]);

然后,在控制器的登录方法中,您可以使用 getLoginRedirect() 从查询字符串参数中安全地获取重定向目标

public function login()
{
    $result = $this->Authentication->getResult();

    // Regardless of POST or GET, redirect if user is logged in
    if ($result->isValid()) {
        // Use the redirect parameter if present.
        $target = $this->Authentication->getLoginRedirect();
        if (!$target) {
            $target = ['controller' => 'Pages', 'action' => 'display', 'home'];
        }
        return $this->redirect($target);
    }
}

拥有多个身份验证流程

在一个提供 API 和 Web 界面两种功能的应用程序中,您可能希望根据请求是 API 请求还是 Web 请求,使用不同的身份验证配置。例如,您可能对 API 使用 JWT 身份验证,但对 Web 界面使用会话。为了支持此流程,您可以根据 URL 路径或任何其他请求属性返回不同的身份验证服务

public function getAuthenticationService(
    ServerRequestInterface $request
): AuthenticationServiceInterface {
    $service = new AuthenticationService();

    // Configuration common to both the API and web goes here.

    if ($request->getParam('prefix') == 'Api') {
        // Include API specific authenticators
    } else {
        // Web UI specific authenticators.
    }

    return $service;
}