首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

APP 使用 Token 實現自動登錄/保持登錄狀態完整方案

     編輯:程式設計疑難解答

核心流程

┌─────────┐     1. 用戶名/密碼      ┌─────────┐
│ 客戶端  │ ─────────────────────> │ 服務端  │
│ (App)   │                         │ (Server)│
└─────────┘                         └────┬────┘
                                          │
                                    2. 驗證成功
                                    生成 Token (JWT/隨機字符串)
                                    關聯用戶 ID、設備信息、過期時間
                                    存入 Redis/DB (Key: Token, Value: UserInfo)
                                          │
                                    3. 返回 Token
┌─────────┐     4. 保存 Token       ┌────┴────┐
│ 客戶端  │ <───────────────────── │ 服務端  │
│ (App)   │   (HTTP Header/Body)   │ (Server)│
└────┬────┘                         └─────────┘
     │
     │ 5. 本地持久化存儲
     ▼
┌─────────────────────────────────────┐
│ Android: SharedPreferences          │
│ iOS: Keychain (安全) / UserDefaults │
│ Key: "auth_token", Value: "xxx.yyy.zzz"    │
└─────────────────────────────────────┘

┌─────────┐     6. 攜帶 Token 請求     ┌─────────┐
│ 客戶端  │ ─────────────────────────> │ 服務端  │
│ (App)   │ Header: Authorization:    │ (Server)│
└─────────┘   Bearer           └────┬────┘
                                           │
                                    7. 驗證 Token
                                    Redis/DB 查找
                                    檢查過期、撤銷、設備匹配
                                           │
                                    8. 返回業務數據
┌─────────┐     9. 正常響應        ┌────┴────┐
│ 客戶端  │ <───────────────────── │ 服務端  │
│ (App)   │                         │ (Server)│
└─────────┘                         └─────────┘

Token 生成策略對比

類型生成方式驗證方式優點缺點推薦場景
JWT (JSON Web Token)簽名算法 (HS256/RS256) 簽署 Payload本地驗證簽名 + 解析 Payload (無狀態)無需存儲、攜帶用戶信息、跨服務驗證無法主動撤銷 (需黑名單)、Payload 體積大微服務、單點登錄、無狀態架構
隨機字符串 (Opaque Token)CSPRNG 生成 32+ 字節十六進制/Base64Redis/DB 查找 Key (有狀態)可隨時撤銷、Token 短、信息不洩露需存儲、查詢延遲、集群需共享存儲傳統單體、安全要求高、需強制下線
UUID + 設備指紋UUID v4 + 設備信息哈希DB 查找 + 設備匹配綁定設備、防盜用換設備需重新登錄金融/高安全 App

關鍵安全措施

  1. HTTPS 強制:所有傳輸加密,防止 Token 劫持。
  2. 短 Access Token + 長 Refresh Token:
    • Access Token:15-30 分鐘,業務接口使用。
    • Refresh Token:7-30 天,僅用於換取新 Access Token,存儲更安全 (HttpOnly Cookie/Keychain)。
  3. Token 綁定設備指紋:生成時記錄 Device ID、User Agent、IP 段,驗證時比對,異常拒絕/觸發風控。
  4. 敏感操作二次驗證:修改密碼、支付、綁定手機需重新輸入密碼/生物識別/短信碼。
  5. 撤銷機制:登出/修改密碼/風控觸發 → 刪除 Redis Key / 加入 JWT 黑名單 (Redis Set, TTL=Token剩余時間)。

客戶端實現要點

Android (Kotlin)

// 保存 Token
fun saveToken(token: String) {
    val prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE)
    prefs.edit().putString("access_token", token).apply()
    // Refresh Token 存 KeyChain
    KeyChainManager.save("refresh_token", refreshToken)
}

// 請求攔截器
val client = OkHttpClient.Builder()
    .addInterceptor { chain ->
        val token = getAccessToken()
        val request = chain.request().newBuilder()
            .addHeader("Authorization", "Bearer $token")
            .build()
        chain.proceed(request)
    }
    .addInterceptor(RefreshTokenInterceptor()) // 401 自動刷新
    .build()

// 401 自動刷新邏輯
class RefreshTokenInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())
        if (response.code == 401) {
            val newToken = refreshAccessTokenSync() // 同步調用刷新接口
            if (newToken != null) {
                val newRequest = chain.request().newBuilder()
                    .header("Authorization", "Bearer $newToken")
                    .build()
                return chain.proceed(newRequest)
            }
        }
        return response
    }
}

iOS (Swift)

// KeyChain 存儲 (安全)
func saveToken(_ token: String, forKey key: String) {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: token.data(using: .utf8)!,
        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
    ]
    SecItemDelete(query as CFDictionary) // 先刪舊
    SecItemAdd(query as CFDictionary, nil)
}

// Alamofire 請求適配器
class AuthRequestAdapter: RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var request = urlRequest
        if let token = KeyChainManager.get("access_token") {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        return request
    }
}

// 401 重試邏輯
let retryPolicy = RetryPolicy(retryLimit: 1) { (request, response, error) in
    if response?.statusCode == 401 {
        return AuthService.refreshToken().map { _ in true } ?? false
    }
    return false
}

服務端 Redis 存儲結構

# Access Token (短期)
Key: "access_token:{token}"
Value: JSON {userId, deviceId, permissions, exp}
TTL: 1800s (30min)

# Refresh Token (長期)
Key: "refresh_token:{token}"
Value: JSON {userId, deviceId, accessTokenKey, exp}
TTL: 2592000s (30d)

# 用戶所有 Token 索引 (便於強制下線)
Key: "user_tokens:{userId}"
Value: Set {access_token_1, refresh_token_1, ...}

# 黑名單 (JWT 無狀態撤銷)
Key: "token_blacklist:{token}"
Value: "1"
TTL: 同 Token 剩余過期時間

自動登錄邏輯 (App 啟動時)

func checkAutoLogin() {
    guard let refreshToken = KeyChainManager.get("refresh_token") else {
        showLoginScreen(); return
    }
    
    // 靜默嘗試刷新 Access Token
    AuthService.refreshToken(refreshToken) { result in
        switch result {
        case .success(let newAccessToken):
            saveToken(newAccessToken)
            enterMainApp()
        case .failure:
            // Refresh Token 過期/失效,清理本地跳轉登錄
            clearLocalAuth()
            showLoginScreen()
        }
    }
}

總結

Token 自動登錄核心:**短 Access Token + 長 Refresh Token + 安全存儲 + 自動刷新攔截器 + 設備綁定 + 撤銷機制**。服務端推薦有狀態隨機 Token + Redis (可撤銷、安全),微服務架構可用 JWT + Redis 黑名單。客戶端必須實現401 靜默刷新與啟動檢查 Refresh Token邏輯,實現真正的「一次登錄,長期免輸入」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved