JWT

JSON Web Token(JSON Web令牌)

是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

JWT作用:
授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。
信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改。

参考链接:https://blog.csdn.net/Top_L398/article/details/109361680

本项目中,使用JWT实现用户登录,以及后续用户身份验证的功能。

应用—用户登录和验证

1.在API(Controller)层创建接口

1
2
3
4
5
@PostMapping("/user-tokens")
public JsonResponse<String> login(@RequestBody User user) throws Exception{
String token = userService.login(user);
return new JsonResponse<>(token);
}

2.在Service层对user信息进行处理。其中密码部分需要前端对明文密码进行加密,后端拿到之后再进行解密,最后逐一判断,生成并返回token。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public String login(User user) throws Exception{
String phone = user.getPhone() == null ? "" : user.getPhone();
String email = user.getEmail() == null ? "" : user.getEmail();
if(StringUtils.isNullOrEmpty(phone) && StringUtils.isNullOrEmpty(email)){
throw new ConditionException("参数异常!");
}
User dbUser = userDao.getUserByPhoneOrEmail(phone, email);
if(dbUser == null){
throw new ConditionException("当前用户不存在!");
}
String password = user.getPassword();
String rawPassword;
try{
rawPassword = RSAUtil.decrypt(password);
}catch (Exception e){
throw new ConditionException("密码解密失败!");
}
String salt = dbUser.getSalt();
String md5Password = MD5Util.sign(rawPassword, salt, "UTF-8");
if(!md5Password.equals(dbUser.getPassword())){
throw new ConditionException("密码错误!");
}
return TokenUtil.generateToken(dbUser.getId());
}

3.TokenUtil类,用于生成验证用户身份的Token,需要使用JWT生成Token。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class TokenUtil {

private static final String ISSUER = "签发者";

public static String generateToken(Long userId) throws Exception{
Algorithm algorithm = Algorithm.RSA256(RSAUtil.getPublicKey(), RSAUtil.getPrivateKey());
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.HOUR, 1);
return JWT.create().withKeyId(String.valueOf(userId))
.withIssuer(ISSUER)
.withExpiresAt(calendar.getTime())
.sign(algorithm);
}

public static String generateRefreshToken(Long userId) throws Exception{
Algorithm algorithm = Algorithm.RSA256(RSAUtil.getPublicKey(), RSAUtil.getPrivateKey());
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DAY_OF_MONTH, 7);
return JWT.create().withKeyId(String.valueOf(userId))
.withIssuer(ISSUER)
.withExpiresAt(calendar.getTime())
.sign(algorithm);
}

public static Long verifyToken(String token){
try{
Algorithm algorithm = Algorithm.RSA256(RSAUtil.getPublicKey(), RSAUtil.getPrivateKey());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
String userId = jwt.getKeyId();
return Long.valueOf(userId);
}catch (TokenExpiredException e){
throw new ConditionException("555","token过期!");
}catch (Exception e){
throw new ConditionException("非法用户token!");
}
}
}

4.身份认证,每次需要验证用户身份时,向前端获取请求头,分离出其中的信息并验证,从而获取用户身份。第二个方法用于双令牌登录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class UserSupport {

@Autowired
private UserService userService;

public Long getCurrentUserId() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String token = request.getHeader("token");
Long userId = TokenUtil.verifyToken(token);
if(userId < 0) {
throw new ConditionException("非法用户");
}
// this.verifyRefreshToken(userId);
return userId;
}

//验证刷新令牌
private void verifyRefreshToken(Long userId){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
String refreshToken = requestAttributes.getRequest().getHeader("refreshToken");
String dbRefreshToken = userService.getRefreshTokenByUserId(userId);
if(!dbRefreshToken.equals(refreshToken)){
throw new ConditionException("非法用户!");
}
}


}