You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift客户端对接Devise JWT认证的Rails API时出现401未授权问题

问题分析与修复方案

核心问题

  1. 请求参数格式不匹配:Rails Devise默认登录接口期望接收application/x-www-form-urlencoded格式的参数(如user[email]=xxx&user[password]=xxx),但你的Swift代码发送的是JSON格式数据,导致Rails无法解析用户凭证,返回空用户信息。
  2. 响应结构体不匹配: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 03:15:54