Auth(统一身份:AuthUser + Guard)

August 1, 2026 · View on GitHub

在 Swoole 常驻进程 + 协程 下,为 HTTP / WebSocket / Workflow HITL 提供同一套身份层:不可变 AuthUser、可替换的 AuthGuardInterface(默认 JWT),经 FrameworkContext 写入协程 Context(array 快照,可 goApp 透传)。

完整说明:docs/Auth.md


目录

Auth/
├── AuthUser.php              # 不可变身份 VO(toArray / fromArray)
├── AuthException.php         # 鉴权失败;code → HTTP 401/403/500
├── AuthGuardInterface.php    # authenticate + generateToken
├── JwtAuthGuard.php          # 默认 JWT(Library Jwt)
└── README.md

关联(不在本目录):

路径职责
FrameworkContext唯一身份门面:setUser / user / userOrFail / getUserId
AuthenticateMiddleware强制 Bearer
OptionalAuthenticateMiddleware有 token 则验,无则匿名放行
Test/Config/auth.php + component/auth.phpJWT 配置与 auth.guard 组件
src/Stubs/auth.conf.stub.phpcreate 时复制为 Config/auth.php

核心约定

规则说明
Context 只存 arraysetUser__swoolefy_auth_user;禁止存 AuthUser 对象(goApp 会跳过 object)
Guard 可单例,用户不可auth.guard 进程内复用;禁止在 Guard 上缓存当前用户
业务只读 FrameworkContext禁止把 Body/Query 的 userId / uid 当鉴权身份
setUsergetUserId() 仍可读透传头 x-user-id(兼容网关验票)
setUserAuth 优先,Header 兜底
flowchart LR
  HTTP[HTTP Bearer] --> MW[AuthenticateMiddleware]
  WS[WS Handshake] --> Guard
  MW --> Guard[AuthGuardInterface]
  Guard -->|setUser| FC[FrameworkContext]
  FC --> Snap["Context array"]
  FC -->|rewrite| HC[HeaderContext]

快速使用

1. 注册组件

模版:src/Stubs/auth.conf.stub.phpAPP_PATH/Config/auth.php,并在 Config/component/auth.php 注册:

use Swoolefy\Support\Auth\JwtAuthGuard;

$config = include APP_PATH . '/Config/auth.php';

return [
    'auth.guard' => static function () use ($config) {
        return new JwtAuthGuard($config['jwt'] ?? []);
    },
];

环境变量:AUTH_JWT_SECRET(生产必填)、AUTH_JWT_ALGOAUTH_JWT_TTLAUTH_JWT_ISSUERAUTH_JWT_AUDIENCE

2. 路由挂中间件

use Swoolefy\Http\Middleware\AuthenticateMiddleware;
use Swoolefy\Http\Middleware\OptionalAuthenticateMiddleware;

// 强制登录
Route::get('/api/me', [AuthUserController::class, 'me'])
    ->middleware(AuthenticateMiddleware::class);

// 游客 + 登录用户均可访问(有 token 则验)
Route::get('/api/feed', [FeedController::class, 'index'])
    ->middleware(OptionalAuthenticateMiddleware::class);

中间件零参 new;内部 Application::getApp()->get('auth.guard')

3. 登录签发 / 业务读取

use Swoolefy\Core\Application;
use Swoolefy\Support\Auth\AuthUser;
use Swoolefy\Support\FrameworkContext;

/** @var \Swoolefy\Support\Auth\AuthGuardInterface $guard */
$guard = Application::getApp()->get('auth.guard');

$token = $guard->generateToken(new AuthUser(
    userId: 'u1',
    roles: ['operator'],
    tenantId: 't1',
));

// 请求内(中间件已 setUser 后)
$user = FrameworkContext::userOrFail();
$userId = FrameworkContext::getUserId();

goApp(static function (): void {
    // 子协程可读同一身份(array 透传)
    $id = FrameworkContext::getUserId();
});

4. WebSocket / HITL

  • WS:握手 callback 用同一 auth.guard 解析 JWT,忽略 query uid
  • HITL:FrameworkContext::userOrFail() + WorkflowHitlAuth::*ForUser;admin 看 roles,不信任 X-Workflow-Role

联调:GET /api/auth-user/me(见 Test/Controller/AuthUserController.php)。


Guard 语义

authenticate 结果含义
null无凭证(匿名);强制中间件 → 401;可选中间件 → 放行
AuthUser验票成功 → FrameworkContext::setUser
AuthException有凭证但非法/过期 → 401(不可当匿名)

替换实现:实现 AuthGuardInterface,改 component/auth.php 注册即可。


测试

composer test:auth
# 等价:
# phpunit --testsuite unit --filter AuthModuleTest
# phpunit --testsuite coroutine --filter AuthContextGoAppTest
# Http:composer test:http -- --filter AuthUserControllerTest
套件用例
UnitPHPUintTest/Unit/Support/Auth/AuthModuleTest
CoroutinePHPUintTest/Coroutine/Support/Auth/AuthContextGoAppTest
HttpPHPUintTest/Unit/Controller/AuthUserControllerTest(无 Bearer → 401)

刻意不做

不做原因
完整 Gate / Policy / RBAC 注册表当前 hasRole / HITL assignee 即可
OAuth2 / Session GuardAPI 优先 JWT;可后接
WS 每条消息重验 JWT握手绑定即可

禁忌与更多 curl / 配置表见 docs/Auth.md