Vapor中ModelSessionAuthenticator会话认证不匹配、会话无法持久化问题求助
Vapor中ModelSessionAuthenticator会话认证不匹配、会话无法持久化问题求助
我仔细看了你贴的代码和问题描述,发现了几个关键问题导致会话认证无法持久、cookie和会话不匹配的情况,咱们一步步来修复:
1. 会话中间件的顺序错误
在configure(_:)方法里,你添加会话中间件的顺序太晚了。会话中间件需要最先被添加,这样它才能在其他中间件/路由处理请求之前,先解析请求中的cookie,加载对应的会话数据。你的当前代码里,app.middleware.use(app.sessions.middleware)是在autoMigrate()之后、routes()之前添加的,这可能导致会话数据没有被正确加载就进入了路由处理。
修复代码:
func configure(_ app: Application) async throws { // 数据库配置代码保持不变 app.databases.use(DatabaseConfigurationFactory.postgres(configuration: .init( hostname: Environment.get("DATABASE_HOST") ?? "localhost", port: Environment.get("DATABASE_PORT").flatMap(Int.init(_:)) ?? SQLPostgresConfiguration.ianaPortNumber, username: Environment.get("DATABASE_USERNAME") ?? "vapor_username", password: Environment.get("DATABASE_PASSWORD") ?? "vapor_password", database: Environment.get("DATABASE_NAME") ?? "vapor_database", tls: .prefer(try .init(configuration: .clientDefault))) ), as: .psql) app.sessions.use(.memory) // 提前添加会话中间件,确保它是第一个执行的中间件 app.middleware.use(app.sessions.middleware) app.migrations.add(CreateUser()) try await app.autoMigrate() try routes(app) }
2. 错误重写了ModelSessionAuthenticatable的sessionID属性
你的User类里手动定义了var sessionID: UUID {id!},但ModelSessionAuthenticatable协议已经有默认实现,它会自动使用模型的id作为会话认证的标识。手动重写这个属性会干扰会话认证的匹配逻辑,导致无法正确从会话中加载已认证的用户。
修复代码:
final class User: Model, @unchecked Sendable, Authenticatable, ModelSessionAuthenticatable { static let passwordHashKey: KeyPath<User, Field<String>> = \.$passwordHash static let schema = "users" @ID(key: .id) var id: UUID? @Field(key: "password_hash") var passwordHash: String init() {} init(id: UUID, passwordHash: String) { self.id = id self.passwordHash = passwordHash } init?(id: UUID, password: String) { guard let hash = try? Bcrypt.hash(password) else { return nil } self.id = id self.passwordHash = hash } func verify(password: String) -> Bool { (try? Bcrypt.verify(password, created: passwordHash)) == true } // 👇 删除这行手动实现的sessionID // var sessionID: UUID {id!} }
3. 路由中不必要的重复认证逻辑
你的login路由里,每次请求都会手动调用req.session.authenticate(user),这会覆盖会话中的认证状态,而且会干扰SessionAuthenticator的自动认证逻辑。SessionAuthenticator的作用就是:第一次认证后,后续请求会自动从会话中加载已认证的用户,不需要每次手动认证。
优化后的路由代码:
func routes(_ app: Application) throws { // 把SessionAuthenticator和路由逻辑分开,让中间件自动处理认证 let authGroup = app.grouped(User.sessionAuthenticator()) authGroup.get("login") { req in print("new request..........................................") print("当前会话数据: ", req.session.data) print("当前请求Cookie: ", req.cookies) print("已认证用户: ", req.session.authenticated(User.self) as Any) // 只有当没有已认证用户时,才去查询并认证用户 if req.session.authenticated(User.self) == nil { let users = try await User.query(on: req.db).all() if let user = users.first { print("🔍 未检测到已认证用户,开始认证...") req.session.authenticate(user) print("✅ 认证完成,当前已认证用户: ", req.session.authenticated(User.self) as Any) } else { try await User(id: .generateRandom(), password: "abc123")?.create(on: req.db) return HTTPStatus.created } } // 保留你的SessionData测试逻辑 req.session.data["test"] = Date().formatted(date: .omitted, time: .complete) print("📝 已更新会话测试数据") return HTTPStatus.ok } }
4. 验证修复效果
修改完成后重新运行项目:
- 第一次访问
/login:控制台会显示“未检测到已认证用户,开始认证...”,并输出已认证用户信息 - 刷新页面(第二次请求):控制台会直接显示已认证用户实例,同时
req.session.data["test"]会保留/更新时间,说明会话和认证状态都正常持久化了
内容来源于stack exchange
相关产品推荐
相关产品推荐

