feat: 登录成功
This commit is contained in:
parent
3165c1e7ea
commit
f8a6815d98
@ -169,9 +169,11 @@ class NetworkService {
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
|
||||
// 添加认证头
|
||||
APIConfig.authHeaders.forEach { key, value in
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
// 添加认证头 - 排除登录接口
|
||||
if !path.contains("/iam/login/") {
|
||||
APIConfig.authHeaders.forEach { key, value in
|
||||
request.setValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加自定义头(如果提供)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
/// Token管理器
|
||||
/// 负责管理应用的认证令牌,包括验证、刷新和过期处理
|
||||
@ -6,8 +7,9 @@ class TokenManager {
|
||||
/// 单例实例
|
||||
static let shared = TokenManager()
|
||||
|
||||
private let logger = Logger(subsystem: "com.yourapp.tokenmanager", category: "TokenManager")
|
||||
|
||||
/// token有效期阈值(秒),在token即将过期前进行刷新
|
||||
/// 例如:设置为300表示在token过期前5分钟开始刷新
|
||||
private let tokenValidityThreshold: TimeInterval = 300
|
||||
|
||||
/// 私有化初始化方法,确保单例模式
|
||||
@ -17,142 +19,162 @@ class TokenManager {
|
||||
|
||||
/// 检查是否存在有效的访问令牌
|
||||
var hasToken: Bool {
|
||||
return KeychainHelper.getAccessToken()?.isEmpty == false
|
||||
let hasToken = KeychainHelper.getAccessToken()?.isEmpty == false
|
||||
logger.debug("检查token存在状态: \(hasToken ? "存在" : "不存在")")
|
||||
return hasToken
|
||||
}
|
||||
|
||||
// MARK: - Token 验证
|
||||
|
||||
/// 验证并刷新token(如果需要)
|
||||
/// - 检查token是否存在
|
||||
/// - 检查token是否有效
|
||||
/// - 在token即将过期时自动刷新
|
||||
/// - Parameter completion: 完成回调,返回验证/刷新结果
|
||||
/// - isValid: token是否有效
|
||||
/// - error: 错误信息(如果有)
|
||||
func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) {
|
||||
logger.debug("开始验证token状态...")
|
||||
|
||||
// 1. 检查token是否存在
|
||||
guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else {
|
||||
// token不存在,返回未授权错误
|
||||
let error = NSError(
|
||||
domain: "TokenManager",
|
||||
code: 401,
|
||||
userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"]
|
||||
)
|
||||
logger.error("❌ Token验证失败: 未找到访问令牌")
|
||||
completion(false, error)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 检查token是否有效
|
||||
if isTokenValid(token) {
|
||||
// token有效,直接返回成功
|
||||
logger.debug("✅ Token验证通过,无需刷新")
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug("🔄 Token需要刷新,开始刷新流程...")
|
||||
|
||||
// 3. token无效或即将过期,尝试刷新
|
||||
refreshToken { [weak self] success, error in
|
||||
if success {
|
||||
// 刷新成功,返回成功
|
||||
self?.logger.debug("✅ Token刷新成功")
|
||||
completion(true, nil)
|
||||
} else {
|
||||
// 刷新失败,返回错误信息
|
||||
let finalError = error ?? NSError(
|
||||
domain: "TokenManager",
|
||||
code: 401,
|
||||
userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"]
|
||||
)
|
||||
self?.logger.error("❌ Token刷新失败: \(finalError.localizedDescription)")
|
||||
completion(false, finalError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查token是否有效
|
||||
/// - Parameter token: 要检查的token字符串
|
||||
/// - Returns: 如果token有效返回true,否则返回false
|
||||
///
|
||||
/// 该方法会检查token的有效性,包括检查token是否为空、是否过期以及通过网络请求验证token。
|
||||
///
|
||||
/// - Note: 该方法会打印一些调试信息,包括token验证开始、token过期时间等。
|
||||
public func isTokenValid(_ token: String) -> Bool {
|
||||
print("🔍 TokenManager: 开始验证token...")
|
||||
logger.debug("开始验证token有效性...")
|
||||
|
||||
// 1. 基础验证:检查token是否为空
|
||||
guard !token.isEmpty else {
|
||||
print("❌ TokenManager: Token为空")
|
||||
logger.error("❌ Token为空")
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. 检查token是否过期(如果可能)
|
||||
// 2. 检查token是否过期
|
||||
if let expiryDate = getTokenExpiryDate(token) {
|
||||
print("⏰ TokenManager: Token过期时间: \(expiryDate)")
|
||||
logger.debug("Token过期时间: \(expiryDate)")
|
||||
if Date() > expiryDate {
|
||||
print("❌ TokenManager: Token已过期")
|
||||
logger.error("❌ Token已过期")
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否需要刷新(在过期前5分钟)
|
||||
let timeRemaining = expiryDate.timeIntervalSinceNow
|
||||
logger.debug("Token剩余有效时间: \(Int(timeRemaining))秒")
|
||||
|
||||
if timeRemaining < tokenValidityThreshold {
|
||||
logger.debug("⚠️ Token即将过期,需要刷新")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 创建信号量用于同步网络请求
|
||||
// 3. 验证token有效性
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var isValid = false
|
||||
var requestCompleted = false
|
||||
|
||||
print("🌐 TokenManager: 发送验证请求到服务器...")
|
||||
let validationRequest = createValidationRequest(token: token)
|
||||
logger.debug("发送Token验证请求: \(validationRequest.url?.absoluteString ?? "未知URL")")
|
||||
logger.debug("请求头: \(validationRequest.allHTTPHeaderFields ?? [:])")
|
||||
|
||||
// 4. 发送验证请求
|
||||
let task = URLSession.shared.dataTask(with: createValidationRequest(token: token)) { data, response, error in
|
||||
let startTime = Date()
|
||||
|
||||
let task = URLSession.shared.dataTask(with: validationRequest) { data, response, error in
|
||||
defer {
|
||||
requestCompleted = true
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime))
|
||||
|
||||
// 检查网络错误
|
||||
if let error = error {
|
||||
print("❌ TokenManager: 验证请求错误: \(error.localizedDescription)")
|
||||
self.logger.error("❌ Token验证请求错误: \(error.localizedDescription) (耗时: \(responseTime))")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
print("❌ TokenManager: 无效的服务器响应")
|
||||
self.logger.error("❌ 无效的服务器响应 (耗时: \(responseTime))")
|
||||
return
|
||||
}
|
||||
|
||||
print("📡 TokenManager: 服务器响应状态码: \(httpResponse.statusCode)")
|
||||
let statusCode = httpResponse.statusCode
|
||||
self.logger.debug("收到Token验证响应 - 状态码: \(statusCode) (耗时: \(responseTime))")
|
||||
|
||||
// 检查状态码
|
||||
guard (200...299).contains(httpResponse.statusCode) else {
|
||||
print("❌ TokenManager: 服务器返回错误状态码: \(httpResponse.statusCode)")
|
||||
guard (200...299).contains(statusCode) else {
|
||||
self.logger.error("❌ 服务器返回错误状态码: \(statusCode)")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有数据
|
||||
// 检查响应数据
|
||||
if let data = data, !data.isEmpty {
|
||||
do {
|
||||
// 尝试解析响应数据
|
||||
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||
self.logger.debug("Token验证响应数据: \(json)")
|
||||
}
|
||||
|
||||
let response = try JSONDecoder().decode(IdentityCheckResponse.self, from: data)
|
||||
isValid = response.isValid
|
||||
print("✅ TokenManager: Token验证\(isValid ? "成功" : "失败")")
|
||||
self.logger.debug("✅ Token验证\(isValid ? "成功" : "失败")")
|
||||
|
||||
if let userId = response.userId {
|
||||
self.logger.debug("用户ID: \(userId)")
|
||||
}
|
||||
|
||||
if let expiresAt = response.expiresAt {
|
||||
self.logger.debug("Token过期时间: \(expiresAt)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("❌ TokenManager: 解析响应数据失败: \(error.localizedDescription)")
|
||||
self.logger.error("❌ 解析响应数据失败: \(error.localizedDescription)")
|
||||
// 如果解析失败但状态码是200,我们假设token是有效的
|
||||
isValid = true
|
||||
print("ℹ️ TokenManager: 状态码200,假设token有效")
|
||||
self.logger.debug("ℹ️ 状态码200,假设token有效")
|
||||
}
|
||||
} else {
|
||||
// 如果没有返回数据但状态码是200,我们假设token是有效的
|
||||
print("ℹ️ TokenManager: 没有返回数据,但状态码为200,假设token有效")
|
||||
self.logger.debug("ℹ️ 没有返回数据,但状态码为200,假设token有效")
|
||||
isValid = true
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
// 5. 设置超时时间(10秒)
|
||||
// 设置超时时间(15秒)
|
||||
let timeoutResult = semaphore.wait(timeout: .now() + 15)
|
||||
|
||||
// 检查是否超时
|
||||
if !requestCompleted && timeoutResult == .timedOut {
|
||||
print("⚠️ TokenManager: 验证请求超时")
|
||||
logger.error("⚠️ Token验证请求超时")
|
||||
task.cancel()
|
||||
return false
|
||||
}
|
||||
@ -170,22 +192,30 @@ class TokenManager {
|
||||
return request
|
||||
}
|
||||
|
||||
/// 从token中提取过期时间(示例实现)
|
||||
/// 从token中提取过期时间
|
||||
private func getTokenExpiryDate(_ token: String) -> Date? {
|
||||
// 这里需要根据实际的JWT或其他token格式来解析过期时间
|
||||
// 以下是JWT token的示例解析
|
||||
logger.debug("开始解析token过期时间...")
|
||||
|
||||
let parts = token.components(separatedBy: ".")
|
||||
guard parts.count > 1, let payloadData = base64UrlDecode(parts[1]) else {
|
||||
guard parts.count > 1 else {
|
||||
logger.error("❌ 无效的token格式")
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let payloadData = base64UrlDecode(parts[1]) else {
|
||||
logger.error("❌ 无法解码token payload")
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
|
||||
let exp = payload["exp"] as? TimeInterval {
|
||||
return Date(timeIntervalSince1970: exp)
|
||||
let expiryDate = Date(timeIntervalSince1970: exp)
|
||||
logger.debug("✅ 成功解析token过期时间: \(expiryDate)")
|
||||
return expiryDate
|
||||
}
|
||||
} catch {
|
||||
print("❌ TokenManager: 解析token过期时间失败: \(error.localizedDescription)")
|
||||
logger.error("❌ 解析token过期时间失败: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -209,49 +239,65 @@ class TokenManager {
|
||||
}
|
||||
|
||||
/// 刷新token
|
||||
/// - Parameter completion: 刷新完成回调
|
||||
/// - success: 是否刷新成功
|
||||
/// - error: 错误信息(如果有)
|
||||
func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
|
||||
logger.debug("开始刷新token...")
|
||||
|
||||
// 获取刷新令牌
|
||||
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
|
||||
// 没有可用的刷新令牌
|
||||
let error = NSError(
|
||||
domain: "TokenManager",
|
||||
code: 401,
|
||||
userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"]
|
||||
)
|
||||
logger.error("❌ 刷新token失败: 未找到刷新令牌")
|
||||
completion(false, error)
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug("找到刷新令牌,准备请求...")
|
||||
|
||||
// 准备刷新请求参数
|
||||
let parameters: [String: Any] = [
|
||||
"refresh_token": refreshToken,
|
||||
"grant_type": "refresh_token"
|
||||
]
|
||||
|
||||
let url = APIConfig.baseURL + "/v1/iam/access-token-refresh"
|
||||
logger.debug("发送刷新token请求到: \(url)")
|
||||
logger.debug("请求参数: \(parameters)")
|
||||
|
||||
let startTime = Date()
|
||||
|
||||
// 发送刷新请求
|
||||
NetworkService.shared.post(path: "/v1/iam/access-token-refresh", parameters: parameters) {
|
||||
(result: Result<TokenResponse, NetworkError>) in
|
||||
|
||||
let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime))
|
||||
|
||||
switch result {
|
||||
case .success(let tokenResponse):
|
||||
// 1. 保存新的访问令牌
|
||||
KeychainHelper.saveAccessToken(tokenResponse.accessToken)
|
||||
self.logger.debug("✅ Token刷新成功 (耗时: \(responseTime))")
|
||||
self.logger.debug("新的access_token: \(tokenResponse.accessToken.prefix(10))...")
|
||||
|
||||
// 2. 如果返回了新的刷新令牌,也保存起来
|
||||
if let newRefreshToken = tokenResponse.refreshToken {
|
||||
self.logger.debug("新的refresh_token: \(newRefreshToken.prefix(10))...")
|
||||
KeychainHelper.saveRefreshToken(newRefreshToken)
|
||||
}
|
||||
|
||||
print("✅ Token刷新成功")
|
||||
if let expiresIn = tokenResponse.expiresIn {
|
||||
self.logger.debug("Token有效期: \(expiresIn)秒")
|
||||
}
|
||||
|
||||
// 保存新的访问令牌
|
||||
KeychainHelper.saveAccessToken(tokenResponse.accessToken)
|
||||
|
||||
completion(true, nil)
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ Token刷新失败: \(error.localizedDescription)")
|
||||
self.logger.error("❌ Token刷新失败 (耗时: \(responseTime)): \(error.localizedDescription)")
|
||||
|
||||
// 刷新失败,清除本地token,需要用户重新登录
|
||||
self.logger.debug("清除所有token...")
|
||||
KeychainHelper.clearTokens()
|
||||
|
||||
completion(false, error)
|
||||
@ -261,30 +307,30 @@ class TokenManager {
|
||||
|
||||
/// 清除所有存储的 token
|
||||
func clearTokens() {
|
||||
print("🗑️ TokenManager: 清除所有 token")
|
||||
logger.debug("开始清除所有token...")
|
||||
|
||||
// 清除Keychain中的token
|
||||
KeychainHelper.clearTokens()
|
||||
// 清除其他与 token 相关的存储
|
||||
|
||||
// 清除UserDefaults中的token相关信息
|
||||
UserDefaults.standard.removeObject(forKey: "tokenExpiryDate")
|
||||
UserDefaults.standard.synchronize()
|
||||
|
||||
logger.debug("✅ 所有token已清除")
|
||||
|
||||
// 发送登出通知
|
||||
NotificationCenter.default.post(name: .userDidLogout, object: nil)
|
||||
logger.debug("已发送登出通知")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Token响应模型
|
||||
/// 用于解析token刷新接口的响应数据
|
||||
// MARK: - 响应模型
|
||||
private struct TokenResponse: Codable {
|
||||
/// 访问令牌
|
||||
let accessToken: String
|
||||
|
||||
/// 刷新令牌(可选)
|
||||
let refreshToken: String?
|
||||
|
||||
/// 过期时间(秒)
|
||||
let expiresIn: TimeInterval?
|
||||
|
||||
/// 令牌类型(如:Bearer)
|
||||
let tokenType: String?
|
||||
|
||||
// 使用CodingKeys自定义键名映射
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case accessToken = "access_token"
|
||||
case refreshToken = "refresh_token"
|
||||
@ -293,16 +339,9 @@ private struct TokenResponse: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 身份验证响应模型
|
||||
/// 用于解析身份验证接口的响应数据
|
||||
private struct IdentityCheckResponse: Codable {
|
||||
/// 是否有效
|
||||
let isValid: Bool
|
||||
|
||||
/// 用户ID(可选)
|
||||
let userId: String?
|
||||
|
||||
/// 过期时间(可选)
|
||||
let expiresAt: Date?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
@ -313,9 +352,6 @@ private struct IdentityCheckResponse: Codable {
|
||||
}
|
||||
|
||||
// MARK: - 通知名称
|
||||
/// 定义应用中使用的通知名称
|
||||
extension Notification.Name {
|
||||
/// 用户登出通知
|
||||
/// 当token失效或用户主动登出时发送
|
||||
static let userDidLogout = Notification.Name("UserDidLogoutNotification")
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import SwiftUI
|
||||
import AuthenticationServices
|
||||
import CryptoKit
|
||||
import os.log
|
||||
|
||||
/// 自定义的 Apple 登录按钮组件
|
||||
struct AppleSignInButton: View {
|
||||
@ -15,6 +16,12 @@ struct AppleSignInButton: View {
|
||||
/// 按钮文字
|
||||
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",
|
||||
@ -23,48 +30,117 @@ struct AppleSignInButton: View {
|
||||
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) // 使用黑色边框
|
||||
)
|
||||
}
|
||||
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 = String.randomURLSafeString(length: 32)
|
||||
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])
|
||||
controller.delegate = Coordinator(onCompletion: onCompletion)
|
||||
controller.performRequests()
|
||||
|
||||
// 创建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 {
|
||||
@ -73,23 +149,123 @@ struct AppleSignInButton: View {
|
||||
return hashedData.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
// MARK: - 协调器
|
||||
// 使用项目中的现有方法生成随机字符串
|
||||
private func randomNonceString(length: Int) -> String {
|
||||
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
return String((0..<length).map{ _ in letters.randomElement()! })
|
||||
}
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
private class Coordinator: NSObject, ASAuthorizationControllerDelegate {
|
||||
let onCompletion: (Result<ASAuthorization, Error>) -> Void
|
||||
private let logger: Logger
|
||||
|
||||
init(onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void) {
|
||||
init(onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void, logger: Logger) {
|
||||
self.onCompletion = onCompletion
|
||||
self.logger = logger
|
||||
super.init()
|
||||
logger.debug("Coordinator 初始化")
|
||||
}
|
||||
|
||||
// 授权成功回调
|
||||
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,7 +234,7 @@ struct LoginView: View {
|
||||
print(" 请求参数: \(parameters.keys)")
|
||||
|
||||
NetworkService.shared.post(
|
||||
path: "/iam/login/oauth",
|
||||
path: "/iam/login/oauth",
|
||||
parameters: parameters
|
||||
) { (result: Result<AuthResponse, NetworkError>) -> Void in
|
||||
let handleResult: () -> Void = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user