iOS Swift实现Azure AD B2C原生Sign in with Apple(无需网页)可行性咨询
iOS Swift中实现Azure AD B2C原生Sign in with Apple(无WebView)
是的,完全可以实现通过Apple原生系统授权弹窗完成Azure AD B2C登录,无需依赖WebView跳转。核心思路是:先通过Apple的AuthenticationServices框架获取原生授权码,再将该授权码传递给Azure AD B2C的令牌端点,直接换取身份令牌和访问令牌。
实现步骤
1. 集成Apple原生登录,获取授权码
使用Apple官方的AuthenticationServices框架唤起原生授权弹窗,同时遵循PKCE流程生成验证参数,确保请求安全性。
import AuthenticationServices class AppleB2CLoginHandler: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { private var codeVerifier: String? // 启动Apple原生登录流程 func initiateAppleLogin() { // 生成PKCE code verifier和challenge codeVerifier = generatePKCECodeVerifier() guard let codeChallenge = generatePKCECodeChallenge(from: codeVerifier!) else { return } let appleIDProvider = ASAuthorizationAppleIDProvider() let authRequest = appleIDProvider.createRequest() authRequest.requestedScopes = [.fullName, .email] // 传递PKCE challenge给Apple authRequest.nonce = codeChallenge let authController = ASAuthorizationController(authorizationRequests: [authRequest]) authController.delegate = self authController.presentationContextProvider = self authController.performRequests() } // MARK: - ASAuthorizationControllerDelegate func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { guard let appleCredential = authorization.credential as? ASAuthorizationAppleIDCredential, let authCodeData = appleCredential.authorizationCode, let codeVerifier = codeVerifier else { return } let authCode = String(data: authCodeData, encoding: .utf8)! // 将Apple授权码传给Azure AD B2C换取令牌 exchangeAppleCodeForB2CTokens(authCode: authCode, codeVerifier: codeVerifier) } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { // 处理登录失败逻辑 print("Apple登录失败: \(error.localizedDescription)") } // MARK: - ASAuthorizationControllerPresentationContextProviding func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { // 适配iOS 13+的窗口获取逻辑 return UIApplication.shared.connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows.first ?? UIWindow() } // MARK: - PKCE工具方法 private func generatePKCECodeVerifier() -> String { let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~" var verifier = "" for _ in 0..<128 { let randomIndex = Int(arc4random_uniform(UInt32(allowedChars.count))) verifier.append(allowedChars[allowedChars.index(allowedChars.startIndex, offsetBy: randomIndex)]) } return verifier } private func generatePKCECodeChallenge(from verifier: String) -> String? { guard let verifierData = verifier.data(using: .utf8) else { return nil } var sha256Buffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) verifierData.withUnsafeBytes { _ = CC_SHA256($0.baseAddress, CC_LONG(verifierData.count), &sha256Buffer) } let hashData = Data(sha256Buffer) return hashData.base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: "=", with: "") } }
2. 将Apple授权码传给Azure AD B2C换取令牌
构造POST请求调用Azure AD B2C的令牌端点,传入授权码、PKCE验证参数等信息,获取身份令牌(id_token)和访问令牌(access_token)。
private func exchangeAppleCodeForB2CTokens(authCode: String, codeVerifier: String) { let tenantName = "你的B2C租户名称" let signInPolicy = "你的登录策略名称" let clientId = "你的B2C应用客户端ID" let redirectUri = "你的注册重定向URI" // 需与Azure B2C应用配置一致,如msal{clientId}://auth let tokenEndpoint = "https://\(tenantName).b2clogin.com/\(tenantName).onmicrosoft.com/\(signInPolicy)/oauth2/v2.0/token" var request = URLRequest(url: URL(string: tokenEndpoint)!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") let parameters = [ "grant_type": "authorization_code", "client_id": clientId, "code": authCode, "redirect_uri": redirectUri, "scope": "openid offline_access", // 按需添加其他权限范围 "code_verifier": codeVerifier, "provider": "apple" ] // 编码请求参数 let parameterString = parameters.map { key, value in "\(key)=\(value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)" }.joined(separator: "&") request.httpBody = parameterString.data(using: .utf8) URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { print("令牌交换失败: \(error?.localizedDescription ?? "未知错误")") return } do { if let tokenResponse = try JSONSerialization.jsonObject(with: data) as? [String: Any] { if let idToken = tokenResponse["id_token"] as? String, let accessToken = tokenResponse["access_token"] as? String { // 处理令牌:存储到Keychain、更新用户登录状态等 DispatchQueue.main.async { // 主线程更新UI或通知登录成功 } } } } catch { print("令牌响应解析失败: \(error.localizedDescription)") } }.resume() }
关键注意事项
- Azure B2C配置验证: 确保已在Azure门户中启用Sign in with Apple作为身份提供者,配置正确的Apple应用ID、Team ID,并上传Apple私钥;同时注册的重定向URI需与代码中一致。
- Info.plist配置: 添加Apple登录所需的URL Scheme和权限声明:
<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>msal你的客户端ID</string> </array> </dict> </array> <key>NSUserActivityTypes</key> <array> <string>com.apple.developer.applesignin</string> </array> - 持久化登录状态: 可通过
ASAuthorizationAppleIDProvider().getCredentialState(forUserID:)方法检查用户登录状态,实现静默登录。
内容的提问来源于stack exchange,提问作者Radek Novak
相关产品推荐
相关产品推荐

