1. 前言
在系统页面进行业务操作时,有时会突然遇到应用闪退,并被重定向至登录页面,要求重新登录。此问题的出现,通常与系统中用于存储用户ID和token信息的Redis缓存有关。具体来说,这可能是由于token过期所导致的身份验证失效。
要解决这个问题,可以采用两种策略:一是自动刷新token,二是token续约。通过在验证用户权限的同时为用户生成新的token并返回给客户端,可以确保客户端及时更新本地存储的token。此外,设置定时任务来刷新token也是一个有效的方法。这样,即使在token即将过期的情况下,也可以通过增加其有效时间来避免应用闪退的问题。
2. 自动刷新token
自动刷新token是一种后端解决方案,旨在解决token过期问题。后端会检查每个token的过期时间,一旦发现某个token即将过期,就会在请求头中添加一个新的token。前端在接收到请求时,会拦截该请求并从请求头中获取新的token。如果新旧token不一致,前端会直接更新本地的token,从而确保后续请求能够正常进行。这种方法可以有效避免因token过期而导致的系统闪退问题,提高了系统的稳定性和安全性。
2.1 后端实现方案
2.1.1 先引入依赖
<dependencies>
<!-- Java JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version> <!-- 使用最新版本 -->
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version> <!-- 使用最新版本 -->
<optional>true</optional>
</dependency>
<!-- 其他必要的依赖项 -->
</dependencies>
2.1.2 生成token的代码
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
public class TokenGenerator {
// 用于生成Token的密钥
private static final String SECRET = "your_secret_key"; // 请使用一个安全的密钥
/**
* 生成一个新的Token。
*
* @param userId 用户ID
* @return 生成的Token字符串
*/
public static String generateToken(String userId) {
// 设置Token的有效期为1小时(3600秒)
long expirationTime = 3600L;
// 创建Token的主体部分,包括用户ID和过期时间戳
String tokenBody = userId + "|" + System.currentTimeMillis() + "|" + expirationTime;
// 使用Base64对Token的主体部分进行编码,以便在Token中传输
String encodedBody = Base64.getEncoder().encodeToString(tokenBody.getBytes(StandardCharsets.UTF_8));
// 创建Token的头部部分,包括签名算法和密钥标识符(通常为密钥的SHA-256摘要)
String header = "{\"alg\":\"" + SignatureAlgorithm.HS256 + "\",\"typ\":\"JWT\"}";
String encodedHeader = Base64.getEncoder().encodeToString(header.getBytes(StandardCharsets.UTF_8));
// 使用密钥对Token的头部和主体部分进行签名,生成最终的Token
String token = Jwts.builder()
.setHeader(encodedHeader) // 设置头部信息
.setClaims(Claims.builder().subject(encodedBody).expiration(new Date(System.currentTimeMillis() + expirationTime * 1000)).build()) // 设置主体信息和过期时间戳
.signWith(Keys.hmacShaKeyFor(SECRET)) // 使用HMAC SHA-256算法和密钥进行签名
.compact(); // 生成最终的Token字符串
return token; // 返回生成的Token字符串
}
}
2.1.3 测试生成token
import java.util.Base64;
public class TokenGeneratorExample {
public static void main(String[] args) {
String userId = "12345"; // 替换为实际的用户ID
String token = TokenGenerator.generateToken(userId);
System.out.println("Generated Token: " + token);
}
}
2.2 前端续约token
token续约更倾向于采用前端解决方案,即由前端来处理token的过期时间。首先,前端和后端需要协商并确定一个用于token续约的接口。当前端检测到某个token即将过期时,它会向后端发送该token,然后由后端来延长该token的过期时间。
在前端实现方案中,使用的是双Token方式,即access-token(AT)和refresh-token(RT)。而对于纯后端方式,则只使用access-token。
那么,AT和RT之间有什么区别?为什么需要RT?实际上,AT的暴露机会更多,因为每个请求都需要携带它。为了降低被劫持的风险,AT的过期时间通常设置得较短。而RT仅在auth服务中用于刷新AT,因此它的过期时间通常设置得较长,以增加便利性。
AT和RT的设计是为了提高网络传输的安全性。在网络传输过程中,AT容易暴露,因为它的过期时间较短。通过使用AT和RT,可以降低这种风险。这种设计已经成为了一种标准的安全处理方式,就像https之于http一样,无需再探讨其合理性。
2.3 疑问及思考
当用户在前端表单页面填写内容时,如果长时间没有进行请求发送,导致Token过期,后端返回401错误,这是一个常见的问题。下面是对这个问题的重写:
如果前端有一个表单页面,用户在长时间没有发送请求的情况下填写了表单,然后尝试提交,但后端返回了401错误(表示未授权或Token已过期),该如何解决?
对于这种情况,解决方案可以考虑以下几点:
-
对于纯后端解决方案,一种可能的做法是让前端在表单填写过程中进行特殊处理。如果提交表单后返回401错误,前端可以将表单数据存储在本地存储中,然后跳转到登录页面。用户登录成功后,页面将返回表单页面,并从本地存储中取出之前保存的数据,重新填充到表单中。
-
对于前端解决方案,当Token过期时(无论是access-token还是refresh-token),可以采取以下措施:
-
监听refresh-token的过期时间,当它接近过期时,向后端发起请求来刷新refresh-token。这样可以确保在Token过期之前进行刷新,避免提交表单时出现401错误。
-
实现类似草稿箱的功能,将表单数据保存在前端本地存储中。当检测到Token过期时,可以提示用户重新登录,并在登录成功后恢复之前保存的数据。这样即使Token过期,用户也可以重新登录并继续之前未完成的操作。
-
以上解决方案可以帮助解决Token过期导致的问题,提高用户的体验和系统的安全性。
-