feat: 登录成功

This commit is contained in:
jinyaqiu 2025-08-27 14:34:23 +08:00
parent 3165c1e7ea
commit f8a6815d98
4 changed files with 319 additions and 105 deletions

View File

@ -169,9 +169,11 @@ class NetworkService {
request.setValue(value, forHTTPHeaderField: key) request.setValue(value, forHTTPHeaderField: key)
} }
// // -
APIConfig.authHeaders.forEach { key, value in if !path.contains("/iam/login/") {
request.setValue(value, forHTTPHeaderField: key) APIConfig.authHeaders.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
} }
// //

View File

@ -1,4 +1,5 @@
import Foundation import Foundation
import OSLog
/// Token /// Token
/// ///
@ -6,8 +7,9 @@ class TokenManager {
/// ///
static let shared = TokenManager() static let shared = TokenManager()
private let logger = Logger(subsystem: "com.yourapp.tokenmanager", category: "TokenManager")
/// tokentoken /// tokentoken
/// 300token5
private let tokenValidityThreshold: TimeInterval = 300 private let tokenValidityThreshold: TimeInterval = 300
/// ///
@ -17,142 +19,162 @@ class TokenManager {
/// 访 /// 访
var hasToken: Bool { var hasToken: Bool {
return KeychainHelper.getAccessToken()?.isEmpty == false let hasToken = KeychainHelper.getAccessToken()?.isEmpty == false
logger.debug("检查token存在状态: \(hasToken ? "存在" : "不存在")")
return hasToken
} }
// MARK: - Token // MARK: - Token
/// token /// token
/// - token
/// - token
/// - token
/// - Parameter completion: /
/// - isValid: token
/// - error:
func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) { func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) {
logger.debug("开始验证token状态...")
// 1. token // 1. token
guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else { guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else {
// token
let error = NSError( let error = NSError(
domain: "TokenManager", domain: "TokenManager",
code: 401, code: 401,
userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"] userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"]
) )
logger.error("❌ Token验证失败: 未找到访问令牌")
completion(false, error) completion(false, error)
return return
} }
// 2. token // 2. token
if isTokenValid(token) { if isTokenValid(token) {
// token logger.debug("✅ Token验证通过无需刷新")
completion(true, nil) completion(true, nil)
return return
} }
logger.debug("🔄 Token需要刷新开始刷新流程...")
// 3. token // 3. token
refreshToken { [weak self] success, error in refreshToken { [weak self] success, error in
if success { if success {
// self?.logger.debug("✅ Token刷新成功")
completion(true, nil) completion(true, nil)
} else { } else {
//
let finalError = error ?? NSError( let finalError = error ?? NSError(
domain: "TokenManager", domain: "TokenManager",
code: 401, code: 401,
userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"] userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"]
) )
self?.logger.error("❌ Token刷新失败: \(finalError.localizedDescription)")
completion(false, finalError) completion(false, finalError)
} }
} }
} }
/// token /// token
/// - Parameter token: token
/// - Returns: tokentruefalse
///
/// tokentokentoken
///
/// - Note: tokentoken
public func isTokenValid(_ token: String) -> Bool { public func isTokenValid(_ token: String) -> Bool {
print("🔍 TokenManager: 开始验证token...") logger.debug("开始验证token有效性...")
// 1. token // 1. token
guard !token.isEmpty else { guard !token.isEmpty else {
print("❌ TokenManager: Token为空") logger.error("❌ Token为空")
return false return false
} }
// 2. token // 2. token
if let expiryDate = getTokenExpiryDate(token) { if let expiryDate = getTokenExpiryDate(token) {
print("⏰ TokenManager: Token过期时间: \(expiryDate)") logger.debug("Token过期时间: \(expiryDate)")
if Date() > 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 return false
} }
} }
// 3. // 3. token
let semaphore = DispatchSemaphore(value: 0) let semaphore = DispatchSemaphore(value: 0)
var isValid = false var isValid = false
var requestCompleted = false var requestCompleted = false
print("🌐 TokenManager: 发送验证请求到服务器...") let validationRequest = createValidationRequest(token: token)
logger.debug("发送Token验证请求: \(validationRequest.url?.absoluteString ?? "未知URL")")
logger.debug("请求头: \(validationRequest.allHTTPHeaderFields ?? [:])")
// 4. let startTime = Date()
let task = URLSession.shared.dataTask(with: createValidationRequest(token: token)) { data, response, error in
let task = URLSession.shared.dataTask(with: validationRequest) { data, response, error in
defer { defer {
requestCompleted = true requestCompleted = true
semaphore.signal() semaphore.signal()
} }
let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime))
// //
if let error = error { if let error = error {
print("❌ TokenManager: 验证请求错误: \(error.localizedDescription)") self.logger.error("❌ Token验证请求错误: \(error.localizedDescription) (耗时: \(responseTime))")
return return
} }
// //
guard let httpResponse = response as? HTTPURLResponse else { guard let httpResponse = response as? HTTPURLResponse else {
print("❌ TokenManager: 无效的服务器响应") self.logger.error("❌ 无效的服务器响应 (耗时: \(responseTime))")
return return
} }
print("📡 TokenManager: 服务器响应状态码: \(httpResponse.statusCode)") let statusCode = httpResponse.statusCode
self.logger.debug("收到Token验证响应 - 状态码: \(statusCode) (耗时: \(responseTime))")
// //
guard (200...299).contains(httpResponse.statusCode) else { guard (200...299).contains(statusCode) else {
print("❌ TokenManager: 服务器返回错误状态码: \(httpResponse.statusCode)") self.logger.error("❌ 服务器返回错误状态码: \(statusCode)")
return return
} }
// //
if let data = data, !data.isEmpty { if let data = data, !data.isEmpty {
do { 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) let response = try JSONDecoder().decode(IdentityCheckResponse.self, from: data)
isValid = response.isValid 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 { } catch {
print("❌ TokenManager: 解析响应数据失败: \(error.localizedDescription)") self.logger.error(" 解析响应数据失败: \(error.localizedDescription)")
// 200token // 200token
isValid = true isValid = true
print(" TokenManager: 状态码200假设token有效") self.logger.debug(" 状态码200假设token有效")
} }
} else { } else {
// 200token self.logger.debug(" 没有返回数据但状态码为200假设token有效")
print(" TokenManager: 没有返回数据但状态码为200假设token有效")
isValid = true isValid = true
} }
} }
task.resume() task.resume()
// 5. 10 // 15
let timeoutResult = semaphore.wait(timeout: .now() + 15) let timeoutResult = semaphore.wait(timeout: .now() + 15)
// //
if !requestCompleted && timeoutResult == .timedOut { if !requestCompleted && timeoutResult == .timedOut {
print("⚠️ TokenManager: 验证请求超时") logger.error("⚠️ Token验证请求超时")
task.cancel() task.cancel()
return false return false
} }
@ -170,22 +192,30 @@ class TokenManager {
return request return request
} }
/// token /// token
private func getTokenExpiryDate(_ token: String) -> Date? { private func getTokenExpiryDate(_ token: String) -> Date? {
// JWTtoken logger.debug("开始解析token过期时间...")
// JWT token
let parts = token.components(separatedBy: ".") 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 return nil
} }
do { do {
if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any], if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
let exp = payload["exp"] as? TimeInterval { let exp = payload["exp"] as? TimeInterval {
return Date(timeIntervalSince1970: exp) let expiryDate = Date(timeIntervalSince1970: exp)
logger.debug("✅ 成功解析token过期时间: \(expiryDate)")
return expiryDate
} }
} catch { } catch {
print("❌ TokenManager: 解析token过期时间失败: \(error.localizedDescription)") logger.error(" 解析token过期时间失败: \(error.localizedDescription)")
} }
return nil return nil
@ -209,49 +239,65 @@ class TokenManager {
} }
/// token /// token
/// - Parameter completion:
/// - success:
/// - error:
func refreshToken(completion: @escaping (Bool, Error?) -> Void) { func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
logger.debug("开始刷新token...")
// //
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else { guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
//
let error = NSError( let error = NSError(
domain: "TokenManager", domain: "TokenManager",
code: 401, code: 401,
userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"] userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"]
) )
logger.error("❌ 刷新token失败: 未找到刷新令牌")
completion(false, error) completion(false, error)
return return
} }
logger.debug("找到刷新令牌,准备请求...")
// //
let parameters: [String: Any] = [ let parameters: [String: Any] = [
"refresh_token": refreshToken, "refresh_token": refreshToken,
"grant_type": "refresh_token" "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) { NetworkService.shared.post(path: "/v1/iam/access-token-refresh", parameters: parameters) {
(result: Result<TokenResponse, NetworkError>) in (result: Result<TokenResponse, NetworkError>) in
let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime))
switch result { switch result {
case .success(let tokenResponse): case .success(let tokenResponse):
// 1. 访 self.logger.debug("✅ Token刷新成功 (耗时: \(responseTime))")
KeychainHelper.saveAccessToken(tokenResponse.accessToken) self.logger.debug("新的access_token: \(tokenResponse.accessToken.prefix(10))...")
// 2.
if let newRefreshToken = tokenResponse.refreshToken { if let newRefreshToken = tokenResponse.refreshToken {
self.logger.debug("新的refresh_token: \(newRefreshToken.prefix(10))...")
KeychainHelper.saveRefreshToken(newRefreshToken) KeychainHelper.saveRefreshToken(newRefreshToken)
} }
print("✅ Token刷新成功") if let expiresIn = tokenResponse.expiresIn {
self.logger.debug("Token有效期: \(expiresIn)")
}
// 访
KeychainHelper.saveAccessToken(tokenResponse.accessToken)
completion(true, nil) completion(true, nil)
case .failure(let error): case .failure(let error):
print("❌ Token刷新失败: \(error.localizedDescription)") self.logger.error("❌ Token刷新失败 (耗时: \(responseTime)): \(error.localizedDescription)")
// token // token
self.logger.debug("清除所有token...")
KeychainHelper.clearTokens() KeychainHelper.clearTokens()
completion(false, error) completion(false, error)
@ -261,30 +307,30 @@ class TokenManager {
/// token /// token
func clearTokens() { func clearTokens() {
print("🗑️ TokenManager: 清除所有 token") logger.debug("开始清除所有token...")
// Keychaintoken
KeychainHelper.clearTokens() KeychainHelper.clearTokens()
// token
// UserDefaultstoken
UserDefaults.standard.removeObject(forKey: "tokenExpiryDate") UserDefaults.standard.removeObject(forKey: "tokenExpiryDate")
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
logger.debug("✅ 所有token已清除")
//
NotificationCenter.default.post(name: .userDidLogout, object: nil)
logger.debug("已发送登出通知")
} }
} }
// MARK: - Token // MARK: -
/// token
private struct TokenResponse: Codable { private struct TokenResponse: Codable {
/// 访
let accessToken: String let accessToken: String
///
let refreshToken: String? let refreshToken: String?
///
let expiresIn: TimeInterval? let expiresIn: TimeInterval?
/// Bearer
let tokenType: String? let tokenType: String?
// 使CodingKeys
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case accessToken = "access_token" case accessToken = "access_token"
case refreshToken = "refresh_token" case refreshToken = "refresh_token"
@ -293,16 +339,9 @@ private struct TokenResponse: Codable {
} }
} }
// MARK: -
///
private struct IdentityCheckResponse: Codable { private struct IdentityCheckResponse: Codable {
///
let isValid: Bool let isValid: Bool
/// ID
let userId: String? let userId: String?
///
let expiresAt: Date? let expiresAt: Date?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
@ -313,9 +352,6 @@ private struct IdentityCheckResponse: Codable {
} }
// MARK: - // MARK: -
/// 使
extension Notification.Name { extension Notification.Name {
///
/// token
static let userDidLogout = Notification.Name("UserDidLogoutNotification") static let userDidLogout = Notification.Name("UserDidLogoutNotification")
} }

View File

@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
import AuthenticationServices import AuthenticationServices
import CryptoKit import CryptoKit
import os.log
/// Apple /// Apple
struct AppleSignInButton: View { struct AppleSignInButton: View {
@ -15,6 +16,12 @@ struct AppleSignInButton: View {
/// ///
let buttonText: String let buttonText: String
//
private let logger = Logger(subsystem: "com.yourapp", category: "AppleSignInButton")
// coordinator
@State private var coordinator: Coordinator?
// MARK: - // MARK: -
init(buttonText: String = "Continue with Apple", init(buttonText: String = "Continue with Apple",
@ -23,48 +30,117 @@ struct AppleSignInButton: View {
self.buttonText = buttonText self.buttonText = buttonText
self.onRequest = onRequest self.onRequest = onRequest
self.onCompletion = onCompletion self.onCompletion = onCompletion
logger.debug("AppleSignInButton 初始化,按钮文字: \(buttonText)")
} }
// MARK: - // MARK: -
var body: some View { var body: some View {
Button(action: handleSignIn) { Button(action: handleSignIn) {
HStack(alignment: .center, spacing: 8) { HStack(alignment: .center, spacing: 8) {
Image(systemName: "applelogo") Image(systemName: "applelogo")
.font(.system(size: 20, weight: .regular)) .font(.system(size: 20, weight: .regular))
Text(buttonText) Text(buttonText)
.font(.system(size: 18, weight: .regular)) .font(.system(size: 18, weight: .regular))
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 60) .frame(height: 60)
.background(Color.white) .background(Color.white)
.foregroundColor(.black) .foregroundColor(.black)
.cornerRadius(30) .cornerRadius(30)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 30) RoundedRectangle(cornerRadius: 30)
.stroke(Color.black, lineWidth: 1) // 使 .stroke(Color.black, lineWidth: 1)
) )
} }
} }
// MARK: - // MARK: -
private func handleSignIn() { private func handleSignIn() {
logger.debug("🍎 用户点击了Apple登录按钮")
let provider = ASAuthorizationAppleIDProvider() let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest() let request = provider.createRequest()
request.requestedScopes = [.fullName, .email] request.requestedScopes = [.fullName, .email]
// nonce // nonce
let nonce = String.randomURLSafeString(length: 32) let nonce = randomNonceString(length: 32)
logger.debug("🔑 生成Nonce: \(nonce)")
request.nonce = sha256(nonce) request.nonce = sha256(nonce)
logger.debug("🔐 Nonce的SHA256: \(request.nonce ?? "nil")")
// //
logger.debug("📞 调用onRequest回调")
onRequest(request) onRequest(request)
// //
logger.debug("🔄 创建ASAuthorizationController")
let controller = ASAuthorizationController(authorizationRequests: [request]) 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 { private func sha256(_ input: String) -> String {
@ -73,23 +149,123 @@ struct AppleSignInButton: View {
return hashedData.compactMap { String(format: "%02x", $0) }.joined() 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 { private class Coordinator: NSObject, ASAuthorizationControllerDelegate {
let onCompletion: (Result<ASAuthorization, Error>) -> Void 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.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)) 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)) 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
}
}
} }

View File

@ -234,7 +234,7 @@ struct LoginView: View {
print(" 请求参数: \(parameters.keys)") print(" 请求参数: \(parameters.keys)")
NetworkService.shared.post( NetworkService.shared.post(
path: "/iam/login/oauth", path: "/iam/login/oauth",
parameters: parameters parameters: parameters
) { (result: Result<AuthResponse, NetworkError>) -> Void in ) { (result: Result<AuthResponse, NetworkError>) -> Void in
let handleResult: () -> Void = { let handleResult: () -> Void = {