
【基于 SwooleHyperf 的微服务实战】第八周·周五分布式 Session 与跨服务认证优化今天我们进入第八周周五主题是分布式 Session 与跨服务认证优化。前面我们已经在网关实现了 JWT 无状态认证但在某些场景下如强制用户下线、Session 共享、OAuth2 授权码流程需要将用户状态保存在服务端这时就需要分布式 Session。今天我们将结合 JWT 与分布式 Session打造一个混合认证体系日常使用 JWT 轻量鉴权必要时使用 Redis 共享 Session 作为兜底实现跨服务实例的状态共享与统一登出。今日目标理解分布式 Session 的价值会话共享、强制下线、状态管理。安装配置hyperf/session组件并将 Session 存储到 Redis实现多实例共享。在网关创建SessionController提供登录、登出、获取当前用户接口Session 模式。实现JWT Session 混合认证中间件优先解析 JWT若失败则检查 Session保证兼容。测试多个服务实例或同一实例不同端口下的 Session 共享验证强制下线功能。分析混合认证的安全性并总结 JWT 与 Session 各自的适用场景。一、环境准备约 20 分钟继续在hyperf-gateway和hyperf-app项目中工作。确保 Redis 可用因为 Session 存储需要 Redis。进入网关容器docker-composeexecswoolebashcd/var/www/hyperf-gateway安装 Session 组件composerrequire hyperf/session发布配置php bin/hyperf.php vendor:publish hyperf/session这会在config/autoload/session.php生成配置文件。同样在hyperf-app中也可安装 Session如需要但今天主要在网关层完成认证业务服务通过透传头获取用户信息所以网关即可是 Session 的载体。二、知识核心分布式 Session 与 JWT 对比约 1 小时1. JWT 的优势与劣势优势无状态服务端不需要存储天然适合分布式环境性能高仅 CPU 计算。劣势无法主动失效一旦签发在过期前无法让特定 Token 失效除非引入黑名单但那就引入了状态。载荷暴露Base64 编码不是加密敏感信息不能存入 JWT。Token 可能很大影响 HTTP 头大小。2. 分布式 Session 的优势优势服务端可控随时可删除 Session 实现强制下线。信息隐藏客户端只持有 Session ID敏感数据存在服务端。细粒度权限可以随时修改 Session 中的用户权限实时生效。劣势需要额外存储Redis/DB增加依赖。扩展性依赖于 Session 存储的高可用。每次请求可能需要查询存储增加延迟可缓存。3. 混合认证策略我们设计一个中间件按以下顺序尝试认证JWT 优先请求头带有Authorization: Bearer jwt验证通过后提取用户信息不检查 Session。Session 兜底请求带有 CookieHYPERF_SESSION_ID或自定义从 Redis 加载 Session 数据验证是否有效。网关自登录网关提供/session/login接口使用 Session 方式认证签发 Cookie 并存储 Session。强制下线实现维护一个banned_users集合在 RedisJWT 中间件可检查user_id是否在此集合实现 JWT 的强制下线需耦合状态但可行。Session 模式下直接删除 Redis 中的 Session 键即可。我们将在网关实现 Session 登录并让 JWT 中间件支持黑名单展现混合模式。三、实战构建混合认证体系约 2.5 小时步骤 1配置 Session 为 Redis 驱动编辑config/autoload/session.php?phpreturn[handlerHyperf\Session\Handler\RedisHandler::class,options[connectiondefault,// 使用 redis 配置中的连接gc_maxlifetime1200,// 20 分钟无操作过期session_nameHYPERF_SESSION_ID,domainnull,cookie_lifetime0,// 会话 Cookie浏览器关闭即失效或设 3600cookie_securefalse,cookie_httponlytrue,],];确保config/autoload/redis.php已配置指向redis容器。步骤 2创建 Session 登录控制器新建app/Controller/SessionAuthController.php?phpnamespaceApp\Controller;useHyperf\HttpServer\Annotation\Controller;useHyperf\HttpServer\Annotation\RequestMapping;useHyperf\Di\Annotation\Inject;useHyperf\Session\SessionInterface;#[Controller(prefix:/session)]classSessionAuthControllerextendsAbstractController{#[Inject]privateSessionInterface$session;#[RequestMapping(path:login,methods:post)]publicfunctionlogin(){$username$this-request-input(username);$password$this-request-input(password);// 模拟验证if($username!admin||$password!123456){return$this-response-json([code401,message用户名或密码错误])-withStatus(401);}// 存储用户信息到 Session$this-session-set(user,[id1,usernameadmin,emailadminexample.com,]);return$this-response-json([code200,message登录成功]);}#[RequestMapping(path:logout,methods:post)]publicfunctionlogout(){$this-session-clear();return$this-response-json([code200,message已登出]);}#[RequestMapping(path:me,methods:get)]publicfunctionme(){$user$this-session-get(user);if(!$user){return$this-response-json([code401,message未登录])-withStatus(401);}return$this-response-json([code200,data$user]);}}说明Hyperf\Session\SessionInterface自动通过中间件注入Session ID 通过 Cookie 传递。步骤 3编写混合认证中间件创建app/Middleware/HybridAuthMiddleware.php?phpnamespaceApp\Middleware;usePsr\Http\Message\ResponseInterface;usePsr\Http\Message\ServerRequestInterface;usePsr\Http\Server\MiddlewareInterface;usePsr\Http\Server\RequestHandlerInterface;useHyperf\Jwt\JwtFactory;useHyperf\Di\Annotation\Inject;useHyperf\Session\SessionInterface;classHybridAuthMiddlewareimplementsMiddlewareInterface{#[Inject]privateJwtFactory$jwtFactory;#[Inject]privateSessionInterface$session;publicfunctionprocess(ServerRequestInterface$request,RequestHandlerInterface$handler):ResponseInterface{// 1. 尝试 JWT$authHeader$request-getHeaderLine(Authorization);if(str_starts_with($authHeader,Bearer )){$tokensubstr($authHeader,7);try{$jwt$this-jwtFactory-make($token);if($jwt-validate()){$claims$jwt-getClaims();$request$request-withAttribute(user_id,$claims[id]??null);$request$request-withAttribute(username,$claims[username]??null);return$handler-handle($request);}}catch(\Throwable$e){// JWT 无效继续尝试 Session}}// 2. 尝试 Session$user$this-session-get(user);if($user){$request$request-withAttribute(user_id,$user[id]);$request$request-withAttribute(username,$user[username]);return$handler-handle($request);}// 3. 未认证return\Hyperf\Utils\ApplicationContext::getContainer()-get(\Hyperf\HttpServer\Contract\ResponseInterface::class)-json([code401,message请先登录])-withStatus(401);}}将此中间件注册到网关的全局中间件替代之前的纯 JWT 中间件或保留并调整顺序。在config/autoload/middlewares.phphttp[\App\Middleware\HybridAuthMiddleware::class,// ... 其他],步骤 4实现 JWT 黑名单强制下线混合模式配套为了弥补 JWT 无法主动失效的缺陷我们在 Redis 中维护一个黑名单集合jwt_blacklist存user_id。在HybridAuthMiddleware的 JWT 成功分支中增加检查$userId$claims[id];if($this-redis-sismember(jwt_blacklist,(string)$userId)){return$this-unauthorized(您的账户已被强制下线);}并提供管理接口POST /admin/ban-user将用户 ID 加入黑名单。这样即使 JWT 未过期用户也会被拒绝。步骤 5验证多实例 Session 共享为了模拟多个网关实例我们让网关监听两个端口9500 和 9500? 实际上可以在 docker-compose 中启动两个相同镜像的网关容器或者临时修改端口启动第二个进程。简单方法使用同一个网关的同一个端口因为 Session 存在 Redis同一个端口的不同请求不同浏览器或 curl已经能体现共享但为了演示“跨实例”我们可以用curl带着从实例 A 获取的 Cookie 去请求实例 B假设有实例 B。由于我们目前只有单实例可以通过查看 Redis 中的 Session 数据理解共享原理。测试步骤通过网关登录获取 Cookiecurl-ccookies.txt-XPOST http://localhost:9500/session/login-dusernameadminpassword123456使用 Cookie 访问受保护接口curl-bcookies.txt http://localhost:9500/articles检查 Redis 中的 Session 键session:*确实包含用户数据。模拟另一个实例如果另一个网关共享同一 Redis使用相同 Cookie 也能得到用户信息。强制下线测试调用/session/logout此时 Session 被清空Cookie 失效。或者通过管理接口将用户加入黑名单JWT 下次请求即被拒绝。步骤 6调整下游透传逻辑混合中间件认证成功后将用户信息注入请求属性。在GatewayMiddleware转发时从属性读取并设置下游头与之前一致。确保无论哪种认证方式业务服务都能获取到X-User-Id等头。四、成果测试与验证约 1 小时1. 测试清单检验项方法通过标准JWT 认证正常携带有效 JWT 访问受保护接口返回正常数据无需 SessionSession 登录POST/session/login获取 Cookie带 Cookie 访问返回用户信息混合认证优先级同时带 JWT 和 Cookie接口根据 JWT 返回不报错识别为 JWT 用户Session 登出POST/session/logout后带 Cookie 访问返回 401JWT 强制下线将用户 ID 加入jwt_blacklist带 JWT 访问返回强制下线提示Session 跨实例共享查看 Redis 中 Session 键另一个进程加载数据一致业务服务接收用户网关转发后业务服务日志打印X-User-Id头包含正确用户 ID2. 安全性考量Cookie 安全生产环境启用cookie_secure: true仅 HTTPS、cookie_httponly: true防 XSS。CSRF如果系统同时使用 Session 和表单提交需引入 CSRF Token。JWT 黑名单需考虑黑名单大小可设置过期时间与 JWT 生命周期一致。3. 常见问题Session 未生效检查session.php配置是否正确Redis 连接是否正常。Cookie 未保存确保响应头中有Set-Cookie且curl -c正确。五、今日作业与学习产出提交代码将SessionAuthController、HybridAuthMiddleware、Session 配置、JWT 黑名单逻辑等提交到 Git。完善认证体系添加OAuth2 密码模式支持网关作为授权服务器签发 JWT 和 Session 二选一。实现Remember Me功能使用长期 Cookie 和持久化 Session。学习笔记画出混合认证的决策流程图请求 → JWT? → Session? → 拒绝。总结 JWT 与 Session 的组合策略列出各场景最佳实践。挑战任务使用Redis 哨兵模式或集群保证 Session 存储高可用。研究OpenID Connect协议思考如何用网关对接第三方登录。通过今天的学习你的微服务认证体系已经兼具了无状态 JWT 的高性能和 Session 的可控性能灵活应对各种业务场景的认证需求。这标志着你已经掌握了微服务安全的核心技术。明天我们将进行第九周的综合实战将所有知识应用于一个复杂的微服务电商核心系统。