JWT 身份验证
注意
Shield 目前仅支持 JWS(已签名 JWT)。不支持 JWE(已加密 JWT)。
什么是 JWT?
JWT 或 JSON Web Token 是一种以 JSON 对象形式在各方之间安全传输信息的紧凑且自包含的方式。它通常用于 Web 应用程序中的身份验证和授权目的。
例如,当用户登录到 Web 应用程序时,服务器会生成一个 JWT 令牌并将其发送给客户端。然后,客户端在对服务器的后续请求的标头中包含此令牌。服务器验证令牌的真实性,并相应地授予对受保护资源的访问权限。
如果您不熟悉 JWT,我们建议您在继续之前查看 JSON Web 令牌简介。
设置
要使用 JWT 认证,您需要额外的设置和配置。
手动设置
-
通过 Composer 安装 "firebase/php-jwt"。
composer require firebase/php-jwt:^6.4
-
将 AuthJWT.php 从 vendor/codeigniter4/shield/src/Config/ 复制到项目的 config 文件夹中,并将命名空间更新为
Config
。您还需要让这些类扩展原始类。请参见以下示例。<?php // app/Config/AuthJWT.php declare(strict_types=1); namespace Config; use CodeIgniter\Shield\Config\AuthJWT as ShieldAuthJWT; /** * JWT Authenticator Configuration */ class AuthJWT extends ShieldAuthJWT { // ... }
-
如果你的 app/Config/Auth.php 没有更新,你也需要更新它。检查 vendor/codeigniter4/shield/src/Config/Auth.php 并应用差异。
你需要添加以下常量
public const RECORD_LOGIN_ATTEMPT_NONE = 0; // Do not record at all public const RECORD_LOGIN_ATTEMPT_FAILURE = 1; // Record only failures public const RECORD_LOGIN_ATTEMPT_ALL = 2; // Record all login attempts
你需要添加 JWT 认证器
use CodeIgniter\Shield\Authentication\Authenticators\JWT; // ... public array $authenticators = [ 'tokens' => AccessTokens::class, 'session' => Session::class, 'jwt' => JWT::class, ];
如果你想在认证链中使用 JWT 认证器,添加
jwt
public array $authenticationChain = [ 'session', 'tokens', 'jwt' ];
配置
根据你的需要配置 app/Config/AuthJWT.php。
设置默认声明
注意
有效负载包含实际传输的数据,例如用户 ID、角色或过期时间。有效负载中的项目称为声明。
将默认有效负载项目设置为属性 $defaultClaims
。
例如
public array $defaultClaims = [
'iss' => 'https://codeigniter.net.cn/',
];
默认声明将包含在 Shield 发出的所有令牌中。
设置密钥
在 $keys
属性中设置你的密钥,或在 .env
文件中设置它。
例如
authjwt.keys.default.0.secret = 8XBFsF6HThIa7OV/bSynahEch+WbKrGcuiJVYPiwqPE=
它至少需要 256 位随机字符串。密钥的长度取决于我们使用的算法。默认算法是 HS256
,因此为了确保哈希值安全且不易猜测,密钥应至少与哈希函数的输出一样长 - 256 位(32 字节)。你可以使用以下命令获取安全的随机字符串
php -r 'echo base64_encode(random_bytes(32));'
注意
密钥用于签名和验证令牌。
登录尝试日志记录
默认情况下,只有登录失败尝试记录在 auth_token_logins
表中。
public int $recordLoginAttempt = Auth::RECORD_LOGIN_ATTEMPT_FAILURE;
如果你不希望有任何日志,请将其设置为 Auth::RECORD_LOGIN_ATTEMPT_NONE
。
如果你想记录所有登录尝试,请将其设置为 Auth::RECORD_LOGIN_ATTEMPT_ALL
。这意味着你记录所有请求。
颁发 JWT
要使用 JWT 认证,你需要一个发出 JWT 的控制器。
这是一个示例控制器。当客户端发布有效的凭据(电子邮件/密码)时,它会返回一个新的 JWT。
// app/Config/Routes.php
$routes->post('auth/jwt', '\App\Controllers\Auth\LoginController::jwtLogin');
<?php
// app/Controllers/Auth/LoginController.php
declare(strict_types=1);
namespace App\Controllers\Auth;
use App\Controllers\BaseController;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Shield\Authentication\Authenticators\Session;
use CodeIgniter\Shield\Authentication\JWTManager;
use CodeIgniter\Shield\Validation\ValidationRules;
class LoginController extends BaseController
{
use ResponseTrait;
/**
* Authenticate Existing User and Issue JWT.
*/
public function jwtLogin(): ResponseInterface
{
// Get the validation rules
$rules = $this->getValidationRules();
// Validate credentials
if (! $this->validateData($this->request->getJSON(true), $rules, [], config('Auth')->DBGroup)) {
return $this->fail(
['errors' => $this->validator->getErrors()],
$this->codes['unauthorized']
);
}
// Get the credentials for login
$credentials = $this->request->getJsonVar(setting('Auth.validFields'));
$credentials = array_filter($credentials);
$credentials['password'] = $this->request->getJsonVar('password');
/** @var Session $authenticator */
$authenticator = auth('session')->getAuthenticator();
// Check the credentials
$result = $authenticator->check($credentials);
// Credentials mismatch.
if (! $result->isOK()) {
// @TODO Record a failed login attempt
return $this->failUnauthorized($result->reason());
}
// Credentials match.
// @TODO Record a successful login attempt
$user = $result->extraInfo();
/** @var JWTManager $manager */
$manager = service('jwtmanager');
// Generate JWT and return to client
$jwt = $manager->generateToken($user);
return $this->respond([
'access_token' => $jwt,
]);
}
/**
* Returns the rules that should be used for validation.
*
* @return array<string, array<string, array<string>|string>>
* @phpstan-return array<string, array<string, string|list<string>>>
*/
protected function getValidationRules(): array
{
$rules = new ValidationRules();
return $rules->getLoginRules();
}
}
你可以通过 curl 发送带有现有用户凭据的请求,如下所示
curl --location 'http://localhost:8080/auth/jwt' \
--header 'Content-Type: application/json' \
--data-raw '{"email" : "[email protected]" , "password" : "passw0rd!"}'
在向 API 发出所有未来请求时,客户端应在 Authorization
标头中以 Bearer
令牌的形式发送 JWT。
你可以通过 curl 发送带有 Authorization
标头的请求,如下所示
curl --location --request GET 'http://localhost:8080/api/users' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTaGllbGQgVGVzdCBBcHAiLCJzdWIiOiIxIiwiaWF0IjoxNjgxODA1OTMwLCJleHAiOjE2ODE4MDk1MzB9.DGpOmRPOBe45whVtEOSt53qJTw_CpH0V8oMoI_gm2XI'
保护路由
指定哪些路由受保护的第一种方法是使用 jwt
控制器过滤器。
例如,为了确保它保护 /api
路由组下的所有路由,你可以在 app/Config/Filters.php 上使用 $filters
设置。
public $filters = [
'jwt' => ['before' => ['api', 'api/*']],
];
您还可以指定过滤器在路由文件本身中的一条或多条路由上运行
$routes->group('api', ['filter' => 'jwt'], static function ($routes) {
// ...
});
$routes->get('users', 'UserController::list', ['filter' => 'jwt']);
当过滤器运行时,它检查 Authorization
头部以查找具有 JWT 的 Bearer
值。然后它验证令牌。如果令牌有效,它可以确定正确的用户,然后可以通过 auth()->user()
调用获得该用户。
方法引用
生成已签名的 JWT
针对特定用户的 JWT
JWT 通过 JWTManager::generateToken()
方法创建。这需要一个用户对象作为第一个参数提供给令牌。它还可以采用可选的附加声明数组、以秒为单位的生存时间、Config\AuthJWT::$keys
中的一个键组(数组键)和附加头数组
public function generateToken(
User $user,
array $claims = [],
?int $ttl = null,
$keyset = 'default',
?array $headers = null
): string
以下代码为用户生成 JWT。
use CodeIgniter\Shield\Authentication\JWTManager;
/** @var JWTManager $manager */
$manager = service('jwtmanager');
$user = auth()->user();
$claims = [
'email' => $user->email,
];
$jwt = $manager->generateToken($user, $claims);
它将 Config\AuthJWT::$defaultClaims
设置为令牌,并在 "sub"
(主题)声明中添加 'email'
声明和用户 ID。如果您未指定,它还会自动设置 "iat"
(签发时间)和 "exp"
(到期时间)声明。
任意 JWT
您可以使用 JWTManager::issue()
方法生成任意 JWT。
它采用 JWT 声明数组,并且可以采用以秒为单位的生存时间、Config\AuthJWT::$keys
中的一个键组(数组键)和附加头数组
public function issue(
array $claims,
?int $ttl = null,
$keyset = 'default',
?array $headers = null
): string
以下代码生成 JWT。
use CodeIgniter\Shield\Authentication\JWTManager;
/** @var JWTManager $manager */
$manager = service('jwtmanager');
$payload = [
'user_id' => '1',
'email' => '[email protected]',
];
$jwt = $manager->issue($payload, DAY);
它使用 Config\AuthJWT::$keys['default']
中的 secret
和 alg
。
它将 Config\AuthJWT::$defaultClaims
设置为令牌,并自动设置 "iat"
(签发时间)和 "exp"
(到期时间)声明,即使您未传递它们。
日志记录
根据上述配置,登录尝试记录在 auth_token_logins
表中。
当记录失败的登录尝试时,发送的原始令牌值保存在 identifier
列中。
当记录成功的登录尝试时,发送的令牌的 SHA256 哈希值保存在 identifier
列中。