Skip to content

添加 JWT 认证

本指南介绍如何为 NENE2 应用程序添加 JWT Bearer 认证——用户注册、登录(令牌签发)和受保护端点。

前提条件:您已有一个运行中的 NENE2 应用程序,至少包含一个路由。 如果没有,请先阅读添加自定义路由


概述

NENE2 提供所需的中间件和接口;您的应用程序提供具体的 JWT 签发器、验证器实现和业务逻辑。

类 / 接口职责
TokenIssuerInterface签发已签名 JWT 的契约
TokenVerifierInterface验证 JWT 并返回 Claims 的契约
LocalBearerTokenVerifier仅用于开发的 HS256 实现(实现两个接口)
BearerTokenMiddleware在请求上强制执行 Bearer 令牌的 PSR-15 中间件
RuntimeApplicationFactory接受任何 MiddlewareInterface 作为 $authMiddleware

第 1 步 — 配置环境变量

.env 中添加 NENE2_LOCAL_JWT_SECRET

dotenv
NENE2_LOCAL_JWT_SECRET=your-local-dev-secret-at-least-32-chars

第 2 步 — 创建 Auth 域

2a — User 实体

php
final readonly class User
{
    public function __construct(
        public int    $id,
        public string $email,
        public string $passwordHash,
    ) {}
}

2b — 仓库接口

php
interface UserRepositoryInterface
{
    public function findByEmail(string $email): ?User;
    public function create(string $email, string $passwordHash): User;
}

2c — 注册用例

php
final class RegisterUseCase
{
    public function __construct(
        private UserRepositoryInterface $users,
        private TokenIssuerInterface    $issuer,
    ) {}

    /** @return array{token: string} */
    public function execute(string $email, string $password): array
    {
        // 验证、重复检查、密码哈希 ...
        $user  = $this->users->create($email, password_hash($password, PASSWORD_BCRYPT));
        $token = $this->issuer->issue(['sub' => (string) $user->id, 'email' => $user->email]);

        return ['token' => $token];
    }
}

2d — 登录用例

php
final class LoginUseCase
{
    public function __construct(
        private UserRepositoryInterface $users,
        private TokenIssuerInterface    $issuer,
    ) {}

    /** @return array{token: string} */
    public function execute(string $email, string $password): array
    {
        $user = $this->users->findByEmail($email);

        if ($user === null || !password_verify($password, $user->passwordHash)) {
            throw new ValidationException([
                new ValidationError('credentials', 'Invalid email or password.', 'invalid_credentials'),
            ]);
        }

        return ['token' => $this->issuer->issue(['sub' => (string) $user->id])];
    }
}

第 3 步 — 接入 Bearer 中间件

使用 $excludedPaths 保护公开 Auth 端点之外的所有路由:

php
$authMiddleware = new BearerTokenMiddleware(
    problemDetails: $problems,
    verifier:       $verifier,
    excludedPaths:  ['/auth/register', '/auth/login'],
);

$app = (new RuntimeApplicationFactory(
    $psr17,
    $psr17,
    routeRegistrars: [$authRegistrar, $taskRegistrar],
    authMiddleware:  $authMiddleware,
))->create();

第 4 步 — 在处理器中读取 Claims

php
$claims = $request->getAttribute('nene2.auth.claims');
$userId = (int) ($claims['sub'] ?? 0);

第 5 步 — 对其他用户的资源返回 403

所有权检查应在用例中进行,而非中间件:

php
if ($task->userId !== $requestingUserId) {
    throw new AccessDeniedException();
}

通过 DomainExceptionHandlerInterfaceAccessDeniedException 映射为 403。


生产环境:使用 JWT 库

使用 firebase/php-jwt 等 JWT 库实现 TokenVerifierInterfaceTokenIssuerInterface, 替换 LocalBearerTokenVerifier 注入即可,其他应用代码无需修改。


参考资料

基于 MIT 许可证发布。