import SwiftUI import AuthenticationServices import CryptoKit import os.log /// 自定义的 Apple 登录按钮组件 struct AppleSignInButton: View { // MARK: - 属性 /// 授权请求回调 let onRequest: (ASAuthorizationAppleIDRequest) -> Void /// 授权完成回调 let onCompletion: (Result) -> Void /// 按钮文字 let buttonText: String // 创建日志记录器 private let logger = Logger(subsystem: "com.yourapp", category: "AppleSignInButton") // 添加一个强引用到coordinator @State private var coordinator: Coordinator? // MARK: - 初始化方法 init(buttonText: String = "Continue with Apple", onRequest: @escaping (ASAuthorizationAppleIDRequest) -> Void, onCompletion: @escaping (Result) -> Void) { self.buttonText = buttonText self.onRequest = onRequest self.onCompletion = onCompletion logger.debug("AppleSignInButton 初始化,按钮文字: \(buttonText)") } // MARK: - 视图主体 var body: some View { Button(action: handleSignIn) { HStack(alignment: .center, spacing: 8) { Image(systemName: "applelogo") .font(.system(size: 20, weight: .regular)) Text(buttonText) .font(.system(size: 18, weight: .regular)) } .frame(maxWidth: .infinity) .frame(height: 60) .background(Color.white) .foregroundColor(.black) .cornerRadius(30) .overlay( RoundedRectangle(cornerRadius: 30) .stroke(Color.black, lineWidth: 1) ) } } // MARK: - 私有方法 private func handleSignIn() { logger.debug("🍎 用户点击了Apple登录按钮") let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() request.requestedScopes = [.fullName, .email] // 创建 nonce 用于安全验证 let nonce = randomNonceString(length: 32) logger.debug("🔑 生成Nonce: \(nonce)") request.nonce = sha256(nonce) logger.debug("🔐 Nonce的SHA256: \(request.nonce ?? "nil")") // 调用请求回调 logger.debug("📞 调用onRequest回调") onRequest(request) // 创建并显示授权控制器 logger.debug("🔄 创建ASAuthorizationController") let controller = ASAuthorizationController(authorizationRequests: [request]) // 创建presentation context provider let presentationContextProvider = PresentationContextProvider() // 创建coordinator并保持强引用 let newCoordinator = Coordinator( onCompletion: { [logger] result in // 处理完成后释放coordinator DispatchQueue.main.async { self.coordinator = nil } switch result { case .success(let auth): logger.debug("✅ 授权成功 - 开始处理授权响应") if let appleIDCredential = auth.credential as? ASAuthorizationAppleIDCredential { let userIdentifier = appleIDCredential.user logger.debug("👤 用户标识符: \(userIdentifier)") // 保存用户ID用于后续验证 UserDefaults.standard.set(userIdentifier, forKey: "appleAuthorizedUserIdKey") } case .failure(let error): let nsError = error as NSError logger.error("❌ 授权失败: \(nsError.localizedDescription)") } // 调用完成回调 self.onCompletion(result) }, logger: logger ) // 保存coordinator的强引用 self.coordinator = newCoordinator // 设置代理和presentation context provider controller.delegate = newCoordinator controller.presentationContextProvider = presentationContextProvider // 执行请求 logger.debug("🚀 开始执行授权请求...") DispatchQueue.main.async { controller.performRequests() } } private func logAppleIDError(_ error: ASAuthorizationError, logger: Logger) { switch error.code { case .canceled: logger.error("❌ 用户取消了授权") case .failed: logger.error("❌ 授权请求失败") case .invalidResponse: logger.error("❌ 无效的授权响应") case .notHandled: logger.error("❌ 授权请求未被处理") case .unknown: logger.error("❌ 未知的授权错误") @unknown default: logger.error("❌ 未处理的授权错误") } } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) return hashedData.compactMap { String(format: "%02x", $0) }.joined() } // 使用项目中的现有方法生成随机字符串 private func randomNonceString(length: Int) -> String { let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" return String((0..) -> Void private let logger: Logger init(onCompletion: @escaping (Result) -> Void, logger: Logger) { self.onCompletion = onCompletion self.logger = logger super.init() logger.debug("Coordinator 初始化") } // 授权成功回调 func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { logger.debug("✅ 授权成功 - 开始处理授权响应") if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { // 创建用户标识符 let userIdentifier = appleIDCredential.user logger.debug("👤 用户标识符: \(userIdentifier)") // 检查是否是新用户 if let email = appleIDCredential.email, let fullName = appleIDCredential.fullName { logger.debug("👋 检测到新用户") logger.debug("📧 新用户邮箱: \(email)") logger.debug("👤 新用户全名: \(fullName.givenName ?? "") \(fullName.familyName ?? "")") } else { logger.debug("👋 现有用户登录") } // 保存用户标识符到UserDefaults UserDefaults.standard.set(userIdentifier, forKey: "appleAuthorizedUserIdKey") // 获取授权码 if let authCodeData = appleIDCredential.authorizationCode, let _ = String(data: authCodeData, encoding: .utf8) { logger.debug("🔑 成功获取授权码") } else { logger.warning("⚠️ 未获取到授权码") } // 获取身份令牌 if let identityTokenData = appleIDCredential.identityToken, let _ = String(data: identityTokenData, encoding: .utf8) { logger.debug("🔐 成功获取身份令牌") } else { logger.error("❌ 获取身份令牌失败") } } else { logger.error("❌ 无法获取有效的 Apple ID 凭证") } // 调用完成处理程序 logger.debug("🔄 调用完成处理程序") onCompletion(.success(authorization)) } // 授权失败回调 func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { let nsError = error as NSError logger.error("❌ 授权失败: \(nsError.localizedDescription)") logger.error("错误域: \(nsError.domain), 错误码: \(nsError.code)") if let appleIDError = error as? ASAuthorizationError { switch appleIDError.code { case .canceled: logger.error("❌ 用户取消了授权") case .failed: logger.error("❌ 授权请求失败") case .invalidResponse: logger.error("❌ 无效的授权响应") case .notHandled: logger.error("❌ 授权请求未被处理") case .unknown: logger.error("❌ 未知的授权错误") @unknown default: logger.error("❌ 未处理的授权错误") } // 记录更多错误详情 if let errorString = (appleIDError as NSError).userInfo[NSDebugDescriptionErrorKey] as? String { logger.error("🔍 错误详情: \(errorString)") } } // 调用完成处理程序 logger.debug("🔄 调用完成处理程序(错误)") onCompletion(.failure(error)) } } // MARK: - Presentation Context Provider private class PresentationContextProvider: NSObject, ASAuthorizationControllerPresentationContextProviding { private let logger = Logger(subsystem: "com.yourapp.applesignin", category: "PresentationContextProvider") override init() { super.init() logger.debug("PresentationContextProvider 初始化") } func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { logger.debug("获取presentation anchor") guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { logger.error("无法获取key window,将使用第一个window") return UIApplication.shared.windows.first ?? UIWindow() } return window } } }