You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Android App基于Spring Boot Server的持久登录(免频繁JWT重登)

解决JWT过期频繁登录的用户体验问题

嘿,这个问题我太熟了——用户刚用没多久就要重新输密码,确实挺劝退的。结合你Spring Boot后端+Android App的场景,给你几个实用的解决方案,都是业内常用的:

1. 核心方案:引入Refresh Token(刷新令牌)

这是解决JWT过期问题的标准操作,思路很简单:

  • 用户首次登录时,后端除了返回有效期1小时的Access Token(用来调用业务接口),还返回一个有效期更长的Refresh Token(比如7天,甚至30天,根据安全需求调整)。
  • 当Access Token过期后,Android端不用让用户输密码,直接用Refresh Token调用后端的刷新接口,换取新的Access Token(甚至新的Refresh Token)。

后端实现示例(Spring Boot)

首先得建个Refresh Token的实体和仓库,用来存储和验证:

@Entity
public class RefreshToken {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String token;
    private Date expiryDate;
    @OneToOne
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private User user;
    // getter、setter省略
}

然后写刷新接口:

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RefreshTokenRepository refreshTokenRepository;
    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/refresh-token")
    public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshTokenRequest request) {
        // 1. 验证Refresh Token是否存在且有效
        RefreshToken refreshToken = refreshTokenRepository.findByToken(request.getRefreshToken())
                .orElseThrow(() -> new RuntimeException("无效的刷新令牌"));

        // 2. 检查Refresh Token是否过期
        if (refreshToken.getExpiryDate().before(new Date())) {
            refreshTokenRepository.delete(refreshToken);
            throw new RuntimeException("刷新令牌已过期,请重新登录");
        }

        // 3. 根据用户信息生成新的Access Token
        User user = refreshToken.getUser();
        UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
        String newAccessToken = jwtTokenUtil.generateToken(userDetails);

        // 4. 可选:滚动刷新Refresh Token(更安全,每次刷新都换个新的)
        String newRefreshToken = jwtTokenUtil.generateRefreshToken(userDetails);
        refreshToken.setToken(newRefreshToken);
        refreshToken.setExpiryDate(new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)); // 7天有效期
        refreshTokenRepository.save(refreshToken);

        return ResponseEntity.ok(new AuthResponse(newAccessToken, newRefreshToken));
    }
}

Android端处理示例(Kotlin)

封装一个OkHttp拦截器,自动处理token过期和刷新:

class AuthInterceptor(private val context: Context) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val accessToken = SecureStorage.getAccessToken(context) // 从安全存储取token

        // 1. 带上Access Token发起原请求
        val authenticatedRequest = originalRequest.newBuilder()
            .addHeader("Authorization", "Bearer $accessToken")
            .build()
        val response = chain.proceed(authenticatedRequest)

        // 2. 如果返回401(token过期),尝试刷新
        if (response.code == 401) {
            response.close() // 关闭旧响应
            val refreshToken = SecureStorage.getRefreshToken(context) ?: run {
                navigateToLogin() // 没有refresh token,直接跳登录
                return response
            }

            // 3. 调用刷新接口
            val refreshResponse = ApiClient.authService.refreshToken(
                RefreshTokenRequest(refreshToken)
            ).execute()

            if (refreshResponse.isSuccessful) {
                val newTokens = refreshResponse.body() ?: run {
                    navigateToLogin()
                    return response
                }
                // 4. 保存新的tokens到安全存储
                SecureStorage.saveTokens(context, newTokens.accessToken, newTokens.refreshToken)

                // 5. 重试原请求
                val retryRequest = originalRequest.newBuilder()
                    .removeHeader("Authorization")
                    .addHeader("Authorization", "Bearer ${newTokens.accessToken}")
                    .build()
                return chain.proceed(retryRequest)
            } else {
                // 刷新失败,跳登录页
                navigateToLogin()
            }
        }
        return response
    }

    private fun navigateToLogin() {
        // 跳转到登录Activity的逻辑
        val intent = Intent(context, LoginActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        context.startActivity(intent)
    }
}

2. 优化体验:提前静默刷新

为了让用户完全感知不到token过期,可以在Android端提前检查Access Token的剩余有效期:

  • 用JWT解析库(比如com.auth0:java-jwt)解析token的exp字段,计算剩余时间。
  • 当剩余时间小于阈值(比如5分钟)时,自动调用刷新接口,提前获取新的Access Token。

示例代码(解析token有效期):

fun isTokenExpiringSoon(token: String): Boolean {
    val jwt = JWT.decode(token)
    val expiryTime = jwt.expiresAt.time
    val currentTime = System.currentTimeMillis()
    val threshold = 5 * 60 * 1000 // 5分钟阈值
    return (expiryTime - currentTime) < threshold
}

3. 可选:结合“记住我”功能

给用户提供“记住我”选项:

  • 如果用户勾选,Refresh Token设为较长有效期(比如30天)。
  • 如果没勾选,Refresh Token设为较短有效期(比如1天)。
  • 同时,用户主动退出登录时,要后端删除对应的Refresh Token,防止被滥用。

关键安全注意事项

  • 存储安全:Android端的tokens一定要存在安全存储(比如Jetpack Security的EncryptedSharedPreferences,或者系统Keystore),绝对不能明文存在SharedPreferences或本地文件。
  • HTTPS传输:所有涉及token的请求必须用HTTPS,防止被窃听。
  • Refresh Token滚动刷新:每次刷新都生成新的Refresh Token,旧的立即失效,降低被盗用的风险。
  • 注销机制:用户修改密码、主动退出时,后端要清空该用户的所有Refresh Token。

内容的提问来源于stack exchange,提问作者Shashank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:32:17