Vapor中UserAuthenticator实现Bearer认证始终返回401 Unauthorized
Vapor登录接口返回401且认证器不触发问题排查
问题现象
开发登录端点时参考Bearer认证逻辑实现后,用Postman调用接口始终返回401 Unauthorized,自定义UserAuthenticator内部断点完全不会触发。
现有实现代码
自定义Bearer认证器
struct UserAuthenticator: AsyncBearerAuthenticator { func authenticate(bearer: BearerAuthorization, for request: Request) async throws { typealias User = App.User let loginUser = try request.query.decode(LoginUserDTO.self) guard let user = try await User.query(on: request.db) .filter(\.$email == loginUser.email) .first(), let userId = user.id else { throw Abort(.notFound) } guard let userToken = try await UserToken.query(on: request.db) .filter(\.$user.$id == userId) .first() else { throw Abort(.notFound) } if bearer.token == userToken.value { request.auth.login(loginUser.toUser()) } } }
User模型ModelAuthenticatable扩展
extension User: ModelAuthenticatable { static var usernameKey = \User.$email static var passwordHashKey = \User.$passwordHash func verify(password: String) throws -> Bool { self.passwordHash == password.generateHash(salt: salt) } }
路由配置
let protected = app.grouped(UserAuthenticator()) protected.post("login") { request async throws -> ClientTokenReponse in let user: User = try request.auth.require(User.self) return ClientTokenReponse(token: "token") }
排查步骤
按优先级依次检查:
- 请求头格式校验
Bearer认证器触发的前置条件是请求携带格式正确的Authorization头,格式必须为:
以下错误写法都会导致认证器直接跳过、根本不进入自定义的Authorization: Bearer <有效token字符串>authenticate逻辑,直接返回401:- 头字段名拼写错误
Bearer写成小写bearer或者其他拼写Bearer和后面的token之间没有半角空格、存在多余特殊字符- 完全没传该请求头
- 路由中间件挂载逻辑错误
你把/login接口挂在了Bearer认证保护的路由组下,逻辑完全倒置:- Bearer认证的作用是保护用户登录后、持有有效token才能访问的接口,比如获取个人信息、修改资料这类接口
- 登录接口是用户还未获取token时访问的,本身不应该挂载Bearer认证中间件
- 认证器逻辑设计错误
你当前的Bearer认证器实现不符合设计规范:- Bearer认证的逻辑只需要从请求头拿到token值,查询token是否合法、是否关联有效用户,不需要额外从query参数解码邮箱查用户
- 现有逻辑里查库找用户已有的token时,没有做token有效期、是否吊销等校验,存在安全隐患
- 认证协议混用问题
你实现的ModelAuthenticatable协议是用于用户名密码基础认证的,和当前使用的Bearer认证是两套独立的认证流程,在Bearer认证链路中不会调用该扩展的校验逻辑,不需要在当前场景实现该扩展。 - 登录接口逻辑修正方向
正确的登录流程应该是:- 公开的
/login接口接收用户提交的邮箱、密码参数 - 接口内根据邮箱查用户,调用密码校验方法匹配传入的明文密码
- 校验通过后生成新的用户token,返回给客户端
- 其他需要登录才能访问的接口,再挂载到Bearer认证的保护路由组下,客户端访问这些接口时才需要携带
Authorization: Bearer <token>头
- 公开的
内容的提问来源于stack exchange,提问作者kalafun
相关产品推荐
相关产品推荐

