Swift客户端对接Devise JWT认证的Rails API时出现401未授权问题
问题分析与修复方案
核心问题
- 请求参数格式不匹配:Rails Devise默认登录接口期望接收
application/x-www-form-urlencoded格式的参数(如user[email]=xxx&user[password]=xxx),但你的Swift代码发送的是JSON格式数据,导致Rails无法解析用户凭证,返回空用户信息。 - 响应结构体不匹配:Swift中定义的
LoginResponse与Rails API实际返回的JSON结构完全不符,导致解码失败,触发invalidCredentials错误。
修复步骤
1. 修正请求参数格式(优先解决)
修改AuthService中的login方法,将JSON格式请求改为表单格式:
func login(email: String, password: String, completion: @escaping (Result<String, AuthenticationError>) -> Void) { guard let url = URL(string: "http://localhost:3001/login") else { completion(.failure(.custom(errorMessage: "URL is not correct"))) return } // 编码为表单格式的参数,嵌套在user键下(Devise默认要求) guard let encodedEmail = email.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let encodedPassword = password.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { completion(.failure(.custom(errorMessage: "参数编码失败"))) return } let bodyString = "user[email]=\(encodedEmail)&user[password]=\(encodedPassword)" guard let bodyData = bodyString.data(using: .utf8) else { completion(.failure(.custom(errorMessage: "请求体生成失败"))) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpBody = bodyData URLSession.shared.dataTask(with: request) { (data, response, error) in guard let data = data, error == nil else { completion(.failure(.custom(errorMessage: "无响应数据"))) return } let responseString = String(decoding: data, as: UTF8.self) print("data: \(responseString)") // 安全解码响应 do { let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: data) guard let token = loginResponse.status.data.token else { completion(.failure(.invalidCredentials)) return } completion(.success(token)) } catch { print("解码失败:\(error)") completion(.failure(.invalidCredentials)) } }.resume() }
2. 修正响应结构体定义
根据API实际返回的JSON结构,重新定义Codable结构体:
// 匹配API返回的完整结构 struct LoginResponse: Codable { let status: LoginStatus } struct LoginStatus: Codable { let code: Int let message: String let data: LoginData } struct LoginData: Codable { let user: User let token: String? } struct User: Codable { let id: Int? let email: String? let firstname: String? let lastname: String? }
3. 移除危险的强制解码代码
删除原代码中这行可能导致崩溃的代码:
try! JSONDecoder().decode(LoginResponse.self, from: data)
换成上述步骤中的do-catch安全解码方式。
额外建议
- 避免使用
try!强制解码,始终用do-catch处理解码错误,便于调试。 - 可以在Rails端添加日志,确认是否接收到正确的请求参数,帮助排查问题。
- 如果希望Rails API支持JSON格式的登录请求,可以在Devise配置中添加
config.http_authenticatable_on_xhr = true,并确保控制器允许JSON参数。
内容的提问来源于stack exchange,提问作者jdps
相关产品推荐
相关产品推荐

