OAuth 2.0 和 JWT 授权在 Swift 上的实践指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么 OAuth 2.0 和 JWT 是 iOS 应用的标准

现代移动应用很少独立运行。它们中的大多数与远程 API 交互,需要安全的用户身份验证和授权。在 Apple 生态系统(iOS、macOS、watchOS)中,OAuth 2.0(访问委托协议)和 JWT(JSON Web Token——一种紧凑的令牌格式)的组合已成为事实上的标准。这种组合提供了灵活性、安全性和集成便利性。

在本文中,我们不会深入探讨枯燥的理论。相反,我们将分析 Swift 中的实际实现。您将了解如何通过 OAuth 2.0 获取访问令牌,如何将其安全地存储在 Keychain 中,如何在客户端解码 JWT,以及如何无需用户干预地刷新令牌。所有代码示例都可用于实际项目。



1. Swift 中的 OAuth 2.0 架构:从请求到令牌

首先,我们确定流程。在移动应用中,最常使用的是 授权码流程与 PKCE(Proof Key for Code Exchange)。这是公共客户端最安全的选择(移动应用无法存储密钥)。

让我们创建一个用于 OAuth 配置的结构体:

struct OAuthConfig {    let clientID: String    let redirectURI: String    let authorizationEndpoint: String    let tokenEndpoint: String    let scope: String}

第一步是构建授权 URL。对于 PKCE,我们需要生成 codeVerifier 及其 SHA256 哈希值 codeChallenge:

import Foundationimport CommonCrypto

class PKCEGenerator { static func generateCodeVerifier() -> String { var buffer = [UInt8](repeating: 0, count: 32) _ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer) return Data(buffer).base64URLEncodedString() } static func generateCodeChallenge(from verifier: String) -> String { guard let data = verifier.data(using: .utf8) else { return "" } var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) data.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) } return Data(hash).base64URLEncodedString() }}

extension Data { func base64URLEncodedString() -> String { return self.base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: "=", with: "") }}

现在,我们构建用于在浏览器中打开的 URL(通过 ASWebAuthenticationSession):

func buildAuthorizationURL(config: OAuthConfig, codeChallenge: String) -> URL? {    var components = URLComponents(string: config.authorizationEndpoint)    components?.queryItems = [        URLQueryItem(name: "response_type", value: "code"),        URLQueryItem(name: "client_id", value: config.clientID),        URLQueryItem(name: "redirect_uri", value: config.redirectURI),        URLQueryItem(name: "scope", value: config.scope),        URLQueryItem(name: "code_challenge", value: codeChallenge),        URLQueryItem(name: "code_challenge_method", value: "S256")    ]    return components?.url}

授权成功后,用户将被重定向到带有 code 参数的 redirectURI。此代码用于交换令牌:

func exchangeCodeForToken(config: OAuthConfig, code: String, codeVerifier: String) async throws -> TokenResponse {    let url = URL(string: config.tokenEndpoint)!    var request = URLRequest(url: url)    request.httpMethod = "POST"    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")        let body = "grant_type=authorization_code" +               "&co                

博客

书籍推荐