Refresh Token对移动应用的意义——实质、刷新机制与安全存储

作者: IT Sectr 发布日期: 2026-04-05 阅读时间: 9 分钟

Refresh Token —是一种特殊的长寿命令牌,旨在无需用户重新输入凭据即可获取新的access token。在OAuth 2.0和OpenID Connect架构中,access token具有短的寿命(15–60分钟),而refresh token则具有更长的寿命(从几小时到几个月)。根据IETF RFC 6749, 2012,refresh token可以实现无缝认证:用户只登录一次,应用程序就能自动刷新访问权限,而不伪中断工作。

核心要点

  • Refresh Token —用于无需重新登录即可获取新access token的长寿命令牌
  • 短寿命access token —降低泄露风险:攻击者只能获得15–30分钟的访问权限
  • Token rotation —每次刷新请求返回新的refresh token,旧的被使其无效
  • 安全存储 —iOS Keychain、Android EncryptedSharedPreferences,切勿使用NSUserDefaults
  • Refresh token reuse detection —防护盗窃:如果被盗的refresh token被使用,会话将被锁定

什么是Refresh Token?

Refresh Token —是客户端应用程序在当前令牌过期后获取新access token所使用的凭据。与access token不同,refresh token不会随每个API请求发送——它存储在客户端的安全存储中,仅在访问认证服务器的token端点时使用。

主要思想是将两种不同寿命的令牌分离。短TTL的access token可以减小被截获时的攻击窗口:如果access token被盗,攻击者只能使用它几分钟。Refresh token通过从不随普通请求传输而受到保护——仅通过安全渠道发送到token端点。这使得盗窃它变得更加困难。

根据OAuth Security Workshop, 2025,引入带rotation的refresh token可以将会话受卑风险降低85%,相比于存储单个长寿命access token。

Refresh Token如何工作

刷新过程在客户端收到HTTP 401 Unauthorized响应或检测到access token已过期时启动(在JWT中检查exp)。客户端向服务器的token端点发送POST请求,带有grant_type=refresh_token和refresh token本身作为请求主体。服务器检查refresh token的有效性、刷新时间及其对client_id的归属。如果一切正确——服务器返回新的access token和可选的新refresh token。

令牌刷新流程

刷新请求示意图如下所示:客户端向/oauth/token发送POST,带有参数grant_type=refresh_tokenrefresh_token={token}client_id={id}。服务器返回含有新access token和过期时间的JSON:

json
{
  "access_token": "eyJhbGciOi...nowy-token",
  "token_type": "Bearer",
  "expires_in": 1800,
  "refresh_token": "nowy-refresh-token"
}

Refresh token rotation(返回新的refresh token)由OAuth 2.0 Security Best Current Practice(RFC 9700)推荐。旧的refresh token因此被使其无效。如果攻击者盗窃了旧refresh token并在合法客户端之前使用了它,服务器将检测到重复使用——reuse detection——并锁定整个会话。

Refresh Token与Access Token对比

Access token和refresh token执行不同的功能,具有根本不同的安全特性。Access token是API的临时通行证,refresh token是获取新通行证的长期授权。

参数Access TokenRefresh Token
寿命15–60分钟天、周或月
传输频率每次API请求仅在刷新时
客户端存储内存/短期安全(Keychain / EncryptedSharedPrefs)
作用域特定权限集用户全部权限范围
撤销通过短TTL服务器黑名单/删除
格式JWT或opaque通常为opaque(随机字符串)

为什么access token不能是长寿命的

短TTLaccess token——是有意识的安全妥协。如果access token被盗(通过截获流量、日志泄露、设备上的恶意软件),攻击者能使用它的时间仅限于15–60分钟。Refresh token通过从不随每次请求传输而受到保护——截获它需要针对token端点的定向攻击。根据Auth0 Security Team, 2025,90%的受卑access token是通过不安全的网络连接被截获的——正是凭借其架构,refresh token可以抵御的情况。

Refresh Token的安全性

安全性refresh token——整个认证方案的关键要素。由于refresh token可以在长时间内提供对账户的完全访问,它的保护必须是最大化的。OWASP和OAuth Security Best Practices发布了具体要求。

在移动设备上存储refresh token

正确存储取决于平台。在iOS上——使用kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly访问权的Keychain。这确保在移除设备密码后,令牌不可用。在Android上——使用Android Keystore中主密钥的AndroidX Security Library中的EncryptedSharedPreferences。令牌在文件系统层面上加密,即使有root访问权也无法访问。禁止:将refresh token存储在SharedPreferences、NSUserDefaults、明文文件或未加密的Base64中。

根据Google Security Blog, 2025,使用AES256-GCM的EncryptedSharedPreferences在物理访问设备时,相比普通的SharedPreferences可将令牌泄露风险降低99.7%。为了增强安全性,还建议分离存储空间:access token可以存储在运行内存中(短期访问),refresh token——仅存储在安全的系统存储中(Keychain / Keystore)。如果应用程序收到系统的前台信号,refresh token将被检查有效性,如果需要,在用户开始交互之前进行刷新。

Refresh Token Rotation

Refresh token rotation——是一种机制,即每次access token刷新请求都返回一个新的refresh token,并删除旧的。如果攻击者盗窃了refresh token并使用它,合法客户端将在下次尝试刷新时收到错误——服务器将检测到refresh token已被使用(reuse detection)。Rotation是OAuth 2.0 Security Best Current Practice(RFC 9700)对所有在移动环境中使用长寿命令牌的系统的强制建议。

重复使用检测

检测算法工作原理如下:服务器在数据库中为每个发行的refresh token存储一个“used”标记。在刷新请求时,服务器检查——如果refresh token已被标记为已使用,这意味着尝试重复使用。服务器立即使该会话的所有refresh token无效,并锁定访问。合法用户被重定向到登录页面。这可以防止盗窃refresh token的攻击:攻击者获得访问权限,但会话在检测后立即被锁定。

根据OAuth Security Workshop, 2025,在引入rotation + reuse detection后,通过被盗refresh token发起成功攻击的概率从23%降低到0.3%。为了实现reuse detection,服务器将最后发行的refresh token的散列与client_id一起存储。在刷新请求时,服务器将提交的refresh token与存储的进行比较——如果不匹配,这意味着重复使用,整个令牌链将被删除。

收到invalid_grant错误时,客户端应进行完全登出:清除所有存储的令牌(access和refresh),结束设备上当前的会话,并将用户重定向到登录屏幕。重新认证将创建一个新的令牌链,与之前的无关。忽略此错误并反复尝试刷新将导致通过reuse detection被锁定。

Kotlin实现

在Kotlin中为Android实现客户端令牌刷新的示例。应用程序截获HTTP 401响应,调用刷新请求,并使用新的access token重复原始请求。使用OkHttp Interceptor——这是自动管理令牌而无需在每个请求中重复逻辑的关键组件。

kotlin
class AuthInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val accessToken = getAccessToken()
        val authRequest = request.newBuilder()
            .addHeader("Authorization", "Bearer $accessToken")
            .build()

        val response = chain.proceed(authRequest)
        if (response.code != 401) return response

        // Access token已过期——通过refresh token进行刷新
        val newToken = refreshAccessToken() ?: return response
        return chain.proceed(request.newBuilder()
            .addHeader("Authorization", "Bearer $newToken")
            .build())
    }

    private fun refreshAccessToken(): String? {
        val refreshToken = getRefreshToken() ?: return null
        val client = OkHttpClient()
        val body = FormBody.Builder()
            .add("grant_type", "refresh_token")
            .add("refresh_token", refreshToken)
            .build()

        val request = Request.Builder()
            .url("https://auth.example.com/oauth/token")
            .post(body)
            .build()

        val response = client.newCall(request).execute()
        val json = JSONObject(response.body?.string() ?: return null)
        val newAccessToken = json.getString("access_token")
        // 在rotation时保存新的refresh token
        saveTokens(newAccessToken, json.optString("refresh_token"))
        return newAccessToken
    }
}

常见问题

Refresh token与access token有何区别?

Access token——短寿命令牌,用于访问API,随每个请求传输。Refresh token——长寿命令牌,用于获取新的access token,仅发送到token端点。Refresh token不应对应用程序的普通API端点可用。

Access token应该多久刷新一次?

每次过期时——通常每15–60分钟。客户端应跟踪过期时间(在JWT中检查exp或使用定时器),并提前启动刷新请求,在实际收到401之前。这可以避免在令牌过期时发送的请求中丢失数据。

可以在服务器上撤销refresh token吗?

可以,refresh token可以也应该被撤销。服务器在数据库中存储活跃refresh token(或其散列)的列表。在登出、密码更改或可疑活动时,服务器从数据库中删除记录,下次使用该令牌的刷新请求将返回invalid_grant错误。

如果两个客户端同时使用旧的refresh token会发生什么?

在实现了带reuse detection的rotation情况下:第一个请求成功刷新了令牌,第二个请求将收到invalid_grant错误。服务器还会记录重复使用——会话将被锁定,两个客户端都丢失访问权。用户必须重新登录。这是为了安全而牺牲便利性。

在iOS上如何安全存储refresh token?

应将refresh token存储在Keychain中,使用属性kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly。这确保令牌加密、在移除密码时不可用,并排除通过iCloud同步。严禁使用UserDefaults或CoreData存储令牌。

总结

  • Refresh Token —用于无需重新登录即可刷新access token的长寿命令牌
  • 短TTLaccess token(15–60分钟)可最大程度减小泄露损失
  • Token rotation —每次刷新返回新refresh token,旧的被使其无效
  • Reuse detection —检测令牌盗窃并锁定会话
  • 存储 —iOS Keychain、Android EncryptedSharedPreferences(AES256-GCM)
  • 服务器撤销 —登出或密码更改时从数据库删除refresh token
  • Refresh token 从不随普通API请求传输

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读