Swift 上的 OAuth 2.0 和 JWT 简介
\n\n现代移动和桌面应用程序很少能绕过身份验证。两个关键协议——OAuth 2.0 和 JWT(JSON Web Token)——已成为客户端与服务器之间安全传输数据的事实标准。在本文中,我们将详细讲解如何在 Swift 上为 iOS 和 macOS 平台实现 OAuth 2.0 和 JWT 授权。您将学习如何配置令牌获取流程、将其安全存储在 Keychain 中,并在令牌过期时自动刷新。
\n\n本文面向已熟悉 Swift 和 SwiftUI 基础的初学者开发者。我们将使用 URLSession 进行网络请求,并使用 Keychain 安全存储数据。代码基于 Swift 5.9 编写,兼容 iOS 15+ / macOS 12+。
\n\n什么是 OAuth 2.0 和 JWT?
\n\n在编写代码之前,让我们先了解一些理论知识。
\n\nOAuth 2.0
\nOAuth 2.0 是一种授权协议,允许应用程序在不传递用户登录名和密码的情况下,获取对用户资源的有限访问权限。它使用访问令牌代替。主要参与者包括:
\n- \n
- 资源所有者 — 用户。 \n
- 客户端 — 您的应用程序。 \n
- 授权服务器 — 颁发令牌的服务器。 \n
- 资源服务器 — 提供数据的 API。 \n
移动应用程序最常用的流程是 授权码流程与 PKCE(Proof Key for Code Exchange)。即使没有难以在应用程序中存储的客户端密钥,它也是安全的。
\n\nJWT(JSON Web Token)
\nJWT 是一种紧凑且自包含的令牌格式,包含 JSON 数据。它由三部分组成,用点分隔:
\n- \n
- 头部 — 令牌类型和签名算法。 \n
- 载荷 — 有效数据(声明),例如用户 ID、角色、过期时间。 \n
- 签名 — 用于验证完整性的签名。 \n
JWT 通常用作 OAuth 2.0 中的 访问令牌。它允许服务器在不查询数据库的情况下验证请求的真实性。
\n\nSwift 上的实践实现
\n\n我们将创建一个简单的 AuthService 类来管理身份验证。示例中将使用服务器 https://api.example.com。
步骤 1:通过 OAuth 2.0 获取令牌
\n\n首先,我们实现一个通过授权码获取令牌的方法。假设我们已经通过 Web 视图(例如 ASWebAuthenticationSession)获得了 authorizationCode。
import Foundation\n\nfinal class AuthService {\n static let shared = AuthService()\n private let baseURL = "https://api.example.com"\n private let clientID = "your_client_id"\n private let redirectURI = "yourapp://callback"\n \n // MARK: - 获取令牌\n func exchangeCodeForTokens(code: String, codeVerifier: String) async throws -> TokenResponse {\n let url = URL(string: "\\(baseURL)/oauth/token")!\n var request = URLRequest(url: url)\n request.httpMethod = "POST"\n request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")\n \n let body = [\n "grant_type": "authorization_code",\n "code": code,\n "redirect_uri": redirectURI,\n "client_id": clientID,\n "code_verifier": codeVerifier\n ]\n .map { "\\($0.key)=\\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")" }\n .joined(separator: "&")\n .data(using: .utf8)\n \n request.httpBody = body\n \n let (data, response) = try await URLSession.shared.data(for: request)\n \n guard let httpResponse = response as? HTTPURLResponse,