如何实现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
相关产品推荐
相关产品推荐

