Vapor前后端分离认证:会话持久化与中间件实现疑问
问题描述
我用Vapor开发带登录、注册页和需登录操作的Web前端,同时有一个独立部署的Vapor实例通过REST API处理认证(多端共用)。目前Web端的/login表单会POST请求到API的/auth/login接口,根据响应处理登录结果,但遇到认证持久化的问题:
- 是否需要自定义中间件存储会话令牌,还是后续请求都通过API校验令牌?
- 已在API端启用Bearer认证,登录成功后API返回会话令牌(用于Bearer授权)和
vapor-sessionCookie。我尝试把这个Cookie加到Web端登录后的重定向响应里,希望浏览器后续请求自动携带,但实际浏览器发送的vapor-session值不对,没成功。 - 不确定这个Cookie方案是否合理,也不知道最佳实现方式。
附上部分代码:
API路由配置(不确定是否需要sessionAuthenticator)
auth//.grouped(UserDB.sessionAuthenticator()) .grouped(UserAuthenticator()) .grouped(UserDB.guardMiddleware()) .group("") { authenticated in authenticated.get("me", use: getCurrentUser)
支持Bearer认证的UserAuthenticator(通过数据库查询会话令牌)
struct UserAuthenticator: BearerAuthenticator { typealias User = UserDB func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void> { let tokenHash = SHA256.hash(bearer.token) return request.sessionTokens .find(tokenHash: tokenHash) .flatMap { if let sessionToken = $0 { request.auth.login(sessionToken.user) } return request.eventLoop.makeSucceededVoidFuture() } } }
目前Web端的认证路由没加中间件,是否需要添加并在中间件的authenticate方法里调用API校验?不清楚正确的实现路径。
解决方案
1. 直接放弃vapor-session跨实例共享方案
Vapor的vapor-session Cookie是实例绑定的:每个Vapor实例有独立的会话存储(默认内存,也可配置Redis等),不同实例的会话数据不互通。你把API端的vapor-session Cookie加到Web端响应里,Web端实例根本查不到对应会话数据,自然验证失败,这个方案从根上就不合理。
2. 正确的认证持久化方案:Web端存储Bearer令牌+自定义中间件自动校验
步骤一:Web端安全存储Bearer令牌
登录成功后,从API响应中提取Bearer令牌,将其存入浏览器的HttpOnly+Secure Cookie(比localStorage更安全,可防范XSS攻击)。示例代码:
// Web端处理/login POST请求的handler func loginHandler(_ req: Request) async throws -> Response { let loginData = try req.content.decode(LoginRequest.self) // 调用认证API的登录接口 let apiResponse = try await req.client.post("https://your-auth-api.com/auth/login") { apiReq in try apiReq.content.encode(loginData) } guard apiResponse.status == .ok else { // 登录失败,返回登录页 return req.redirect(to: "/login") } let tokenResponse = try apiResponse.content.decode(TokenResponse.self) // 设置安全Cookie存储令牌 let response = req.redirect(to: "/dashboard") response.cookies.set( Cookie( name: "auth_token", value: tokenResponse.bearerToken, expires: .distantFuture, httpOnly: true, secure: true, sameSite: .lax ) ) return response }
步骤二:Web端自定义认证中间件
编写中间件自动完成以下操作:
- 跳过登录、注册等无需认证的路由
- 从Cookie中提取Bearer令牌
- 调用API的
/me接口校验令牌有效性 - 校验通过后将用户信息存入请求上下文,供后续路由使用
示例代码:
struct WebAuthMiddleware: AsyncMiddleware { func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response { // 定义无需认证的公开路由 let publicPaths = ["/login", "/register", "/"] if publicPaths.contains(request.url.path) { return try await next.respond(to: request) } // 从Cookie中获取令牌 guard let token = request.cookies["auth_token"]?.value else { // 无令牌,重定向到登录页 return request.redirect(to: "/login") } // 调用API校验令牌有效性 let apiResponse = try await request.client.get("https://your-auth-api.com/auth/me") { apiReq in apiReq.headers.bearerAuthorization = .init(token: token) } guard apiResponse.status == .ok else { // 令牌无效,清除Cookie并重定向登录 let response = request.redirect(to: "/login") response.cookies.remove(name: "auth_token") return response } // 将用户信息存入请求上下文,供后续路由调用 let user = try apiResponse.content.decode(UserDB.self) request.storage.set(UserDB.self, to: user) return try await next.respond(to: request) } }
步骤三:Web端路由绑定中间件
将自定义中间件挂载到需要认证的路由组上:
let authenticatedRoutes = app.grouped(WebAuthMiddleware()) authenticatedRoutes.get("/dashboard") { req in // 从请求上下文获取用户信息 let user = req.storage.get(UserDB.self)! return try await req.view.render("dashboard", ["user": user]) }
3. API端路由优化
API端的/me接口已经通过UserAuthenticator和guardMiddleware实现Bearer认证,不需要启用sessionAuthenticator(因为你用的是Bearer令牌而非Vapor原生会话),保持现有路由配置即可。
内容的提问来源于stack exchange,提问作者GP89
相关产品推荐
相关产品推荐

