Swift 中的 OAuth 2.0 和 JWT 简介
现代基于 Swift 的移动和桌面应用程序(iOS/macOS)越来越多地使用 OAuth 2.0 协议和 JWT(JSON Web Token) 格式来实现安全授权。OAuth 2.0 提供对资源的委托访问,而 JWT 允许紧凑且安全地传输用户数据。在本文中,我们将分析如何在 Swift 中实现完整的授权链:从获取令牌到验证和刷新令牌。
我们将讨论:
- OAuth 2.0 基础:流程(flows)和角色
- JWT 结构及其在 Swift 中的解码
- 使用
URLSession实际实现授权 - 在钥匙串(Keychain)中存储令牌及其刷新
Swift 开发者的 OAuth 2.0 基础
OAuth 2.0 是一种授权协议,允许应用程序在不传递用户凭据的情况下获得对用户资源的有限访问权限。在 Swift 应用程序的上下文中,最常使用的是带有 PKCE(Proof Key for Code Exchange)的 Authorization Code Flow,适用于移动应用程序。
OAuth 2.0 的关键组件
- Resource Owner — 用户,数据所有者
- Client — 您的 Swift 应用程序
- Authorization Server — 颁发令牌的服务器(例如 Google、Facebook、您的后端)
- Resource Server — 提供受保护数据的 API
Swift 中的典型流程如下:
- 应用程序打开一个 Web 视图(SFSafariViewController 或 ASWebAuthenticationSession)以进行用户身份验证。
- 成功登录后,服务器重定向到带有临时代码的回调 URL。
- 应用程序将代码交换为 access token 和 refresh token。
- Access token 用于 API 请求,refresh token 用于在 access token 过期时获取新的 access token。
JWT 结构及其在 Swift 中的解码
JWT 由三部分组成,用点分隔:header.payload.signature。JWT 示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikl2YW4gUGV0cm92IiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在 Swift 中解码 payload(不验证签名):
import Foundation
struct JWT {
static func decodePayload(_ token: String) -> [String: Any]? {
let segments = token.components(separatedBy: ".")
guard segments.count == 3 else { return nil }
var base64 = segments[1]
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let length = Double(base64.lengthOfBytes(using: .utf8))
let requiredLength = 4 * ceil(length / 4.0)
let padding = Int(requiredLength - length)
if padding > 0 {
base64 += String(repeating: "=", count: padding)
}
guard let data = Data(base64Encoded: base64) else { return nil }
return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
}
}
// 使用
if let payload = JWT.decodePayload("your.jwt.token") {
print(payload["sub"] ?? "")
print(payload["exp"] ?? "")
}
要在客户端完全验证 JWT 签名,请使用通过 Swift Package Manager 可用的 SwiftJWT 或 JWTDecode 库。
在 Swift 中实现 OAuth 2.0 和 JWT
步骤 1:配置 URLSession 和令牌模型
struct AuthToken: Codable {
let accessToken: String
let refreshToken: String?
let expiresIn: Int
let tokenType: String
}
class AuthService {
private let session = URLSession.shared
```