基于JWT的Symfony无状态API两步身份验证实现咨询
无状态架构下的两步认证实现方案
因为当前应用配置了stateless: true不使用服务端会话,所有认证状态必须通过JWT的自定义声明(claims)来传递,不能依赖Session存储中间认证进度,因此核心思路是拆分JWT的权限等级,将认证流程拆分为两个独立的签发阶段,不需要修改Symfony核心的认证组件逻辑。
这种方案不需要依赖服务端会话存储中间认证态,完全匹配无状态架构要求,相比把两步认证逻辑揉进单个自定义认证器的方案,可维护性更高,也不会出现无状态下无法记录认证进度的问题。
1. 改造第一步json_login的认证成功回调
- 保持现有
json_login的基础配置不变,在用户通过邮箱+密码校验成功后,不直接签发带全量业务权限的正式JWT - 新增对
security.authentication.success事件的监听,识别来自app_login路由的认证成功事件,为用户签发低权限临时JWT:- 令牌内注入自定义声明
mfa_pending: true,标记当前认证进度 - 给令牌分配专属权限
ROLE_MFA_PENDING,不附带任何业务接口访问权限 - 令牌有效期设置为5~10分钟,仅用于走后续OTP校验流程,缩短临时凭证的泄露风险窗口
- 这个临时令牌的签发逻辑可以直接复用现有JWT组件的基础能力,不需要额外引入新的认证组件
- 令牌内注入自定义声明
2. 实现OTP校验接口与正式JWT签发逻辑
- 开发独立的OTP发送、校验接口:
- 用户持临时JWT请求OTP发送接口时,服务端通过JWT解析拿到用户标识,生成带过期时间(比如5分钟)的OTP码,存储在Redis/数据库这类服务端存储中,不把OTP码明文放到JWT里
- 开发OTP提交校验接口,当用户提交的OTP码和服务端存储的匹配校验通过后,重新为用户签发带全量业务权限的正式JWT:
- 移除
mfa_pending标记 - 注入用户实际的业务权限集(比如
ROLE_USER、对应业务角色权限) - 配置符合业务需求的长有效期,支持后续的刷新逻辑
- 移除
3. 调整security.yaml的访问控制规则
按照从上到下的匹配顺序调整防火墙规则,严格拦截权限边界,核心配置片段如下:
security: firewalls: # 保持现有无状态配置,所有接口统一走JWT解析 api: stateless: true json_login: check_path: app_login username_path: email password_path: password jwt: ~ access_control: # 登录、OTP发送等公开/特殊权限接口的匹配规则放在最前面 - { path: ^/api/auth/login, roles: PUBLIC_ACCESS } # 仅允许持临时凭证的用户访问OTP相关接口 - { path: ^/api/auth/otp/, roles: ROLE_MFA_PENDING } # 所有核心业务接口,强制要求正式用户权限,自动拦截带ROLE_MFA_PENDING的临时令牌 - { path: ^/api/, roles: ROLE_USER }
因为Symfony的access_control是按顺序匹配的,临时JWT里只有ROLE_MFA_PENDING权限,没有ROLE_USER权限,所以自然会被业务接口的规则拦截,完全符合“通过第一步密码校验的用户仅持有临时凭证,无法访问全量业务接口”的需求。
4. 关键注意事项
- 临时JWT绝对不要配置刷新令牌权限,避免用户用临时凭证直接走刷新接口跳过OTP校验
- OTP码的存储要带上尝试次数限制,超过错误次数就作废,防止暴力破解
- 所有JWT的密钥要做好保护,不要把OTP码、权限标记这类敏感内容存在可被客户端篡改的声明里
内容的提问来源于stack exchange,提问作者karatas.hakan
相关产品推荐
相关产品推荐

