image.png

1. 什么是JWT https://jwt.io/introduction/

  1. JSON Web令牌(JWT)是一个开放标准([RFC 7519](https://tools.ietf.org/html/rfc7519)),它定义了一种紧凑而独立的方法,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用**HMAC**算法)或使用**RSA**或**ECDSA**的公钥/私钥对对JWT进行**签名**。

通俗解释:

JW简称为Json WebToken,也就是通过json形式作为web应用中的令牌,用于在各方安全的将信息作为json对象传输,在数据传输过程中还可以完成数据加密、签名等相关处理。

2. JWT能做什么

1.授权

这是jwt的常见方案,一旦用户登录,每个后续请求都包括jwt,从而允许用户访问该令牌允许的路由,服务和资源

2.信息交换

image.png

3. 为什么是JWT

image.png
image.png
image.png
image.png

4. JWT的结构

1.令牌组成

1.表头(Header)
2.有效载荷(Playload)
3.签名(Signature)
通常显示为 xxxx.xxxxx.xxxx Header.Playload.Signature

2.标头 Header

标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。
例如:

  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

然后,此JSON被Base64Url编码以形成JWT的第一部分。
注意:Base64是一种编码,也就是说,它是可以被翻译为原来的样子,并不是一种加密过程

3.有效载荷Playload

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明
有三种声明: registered, public, private .同样的它是使用Base64编码组成的JWT结构的第二部分

  1. {
  2. "sub": "1234567890",
  3. "name": "John Doe",
  4. "admin": true
  5. }

4.签名 Signature

前两段都是使用base64进行编码的,即前端可以解开知道里面的信息,Signature需要使用编码后的header和playload以及我们提供的一个密钥,然后使用header中指定的签名算法(HS256)进行签名,签名的作用是保证JWT没有被篡改过

  1. HMACSHA256( base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

5.签名的目的

最后一步签名的过程,实际是对头部以及负载内容进行签名,防止内容被篡改,如果有人对头部以及负载内容解码之后进行修改,在进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的,如果要对新的头部与负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的

6.信息安全问题

Base64是一种编码,是可逆的,那么信息不就被暴露了吗?
image.png
image.png

5. 使用JWT

1.引入依赖

  1. <dependency>
  2. <groupId>com.auth0</groupId>
  3. <artifactId>java-jwt</artifactId>
  4. <version>3.12.0</version>
  5. </dependency>

2.创建token

  1. @Test
  2. void contextLoads() {
  3. HashMap<String,Object> map = new HashMap<>();
  4. Calendar cal = Calendar.getInstance();
  5. // 令牌的获取
  6. cal.add(Calendar.SECOND,5);
  7. String token = JWT.create()
  8. .withHeader(map) // header
  9. .withExpiresAt(cal.getTime()) // 设置过期时间 90s
  10. .withClaim("userId",2342234) //playload 自定义用户名
  11. .withClaim("username","bampu") // playload
  12. .sign(Algorithm.HMAC256("23fs@#asc")); // signture 设置签名密钥
  13. System.out.println(token);
  14. }

生成结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDkwNTk0OTgsInVzZXJJZCI6MjM0MjIzNCwidXNlcm5hbWUiOiJiYW1wdSJ9.EQx_lXMIPlymtNY7sq1IOe4DgmkKeKS5UznNi1BS4vQ

3.根据令牌和签名解析数据

  1. @Test
  2. public void test(){
  3. // 创建验证对象
  4. JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("23fs@#asc")).build();
  5. DecodedJWT vertify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDkwNTk0OTgsInVzZXJJZCI6MjM0MjIzNCwidXNlcm5hbWUiOiJiYW1wdSJ9.EQx_lXMIPlymtNY7sq1IOe4DgmkKeKS5UznNi1BS4vQ");
  6. System.out.println("过期时间:"+vertify.getExpiresAt());
  7. System.out.println(vertify.getClaim("userId").asInt());
  8. System.out.println(vertify.getClaim("username").asString());
  9. }

4.常见异常信息

TokenExpiredException 令牌过期异常
SignatureVerificationException 签名不一致异常
AlgorithmMismatchException 算法不匹配异常
InvalidClaimException 失效的playload异常

5. Springboot整合jwt

1.封装jwt工具类

  1. public class JwtUtils {
  2. private static final String SALT = "j1w4t5@321!";
  3. /**
  4. * 生成token
  5. * header.playload.signature
  6. * @param map 传入 playload
  7. * @return 返回token
  8. */
  9. public static String getToken(Map<String, String> map) {
  10. JWTCreator.Builder builder = JWT.create();
  11. map.forEach((k, v) -> {
  12. builder.withClaim(k, v);
  13. });
  14. Calendar calendar = Calendar.getInstance();
  15. calendar.add(Calendar.DATE, 7 ); // 默认七天过期
  16. builder.withExpiresAt(calendar.getTime());
  17. return builder.sign(Algorithm.HMAC256(SALT));
  18. }
  19. /**
  20. * 验证token
  21. * @param token token
  22. */
  23. public static void verify(String token){
  24. JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
  25. }
  26. /**
  27. * 获取token中的playload
  28. * @param token token
  29. * @return
  30. */
  31. public static DecodedJWT getToken(String token){
  32. return JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
  33. }
  34. }

2.创建实体

  1. public class User {
  2. private Integer id;
  3. private String name;
  4. private String password;
  5. }

3.dao与service实现

  1. @Service
  2. public class UserService {
  3. @Autowired
  4. UserDao userDao;
  5. public User getUser(User user){
  6. User userDB = userDao.login(user);
  7. if (userDB != null) {
  8. return userDB;
  9. }
  10. throw new RuntimeException("认证失败");
  11. }
  12. }

4.controller实现

  1. @RestController
  2. @RequestMapping
  3. public class UserController {
  4. private static final Logger logger = LoggerFactory.getLogger(UserController.class);
  5. @Autowired
  6. private UserService userService;
  7. @PostMapping("/user/login")
  8. public Map<String, Object> login(User user) {
  9. Map<String, Object> map = new HashMap<>();
  10. try {
  11. User userDB = userService.getUser(user);
  12. Map<String, String> payload = new HashMap<>();
  13. payload.put("id", userDB.getId() + "");
  14. payload.put("name", userDB.getName());
  15. String token = JwtUtils.getToken(payload);
  16. map.put("state", true);
  17. map.put("msg", "登录成功");
  18. map.put("token", token);
  19. return map;
  20. } catch (Exception e) {
  21. logger.error("登录失败", e);
  22. map.put("state", false);
  23. map.put("msg", e.getMessage());
  24. map.put("token", "");
  25. }
  26. return map;
  27. }
  28. @PostMapping("/user/test")
  29. public Map<String, Object> test(HttpServletRequest request) {
  30. String token = request.getHeader("token");
  31. DecodedJWT verify = JwtUtils.getToken(token);
  32. String id = verify.getClaim("id").asString();
  33. String name = verify.getClaim("name").asString();
  34. logger.info("用户id:[{}]", id);
  35. logger.info("用户名: [{}]", name);
  36. //TODO 业务逻辑
  37. Map<String, Object> map = new HashMap<>();
  38. map.put("state", true);
  39. map.put("msg", "请求成功");
  40. return map;
  41. }
  42. }

5.登录成功

image.png

6.获取数据

image.png

上述问题每次都要传递token数据,每个方法都需要验证token,代码冗余不够灵活,优化?

使用拦截器优化、springCloud 网关

jwt拦截器实现

  1. package cn.com.beyond.springbootjwt.interceptors;
  2. import cn.com.beyond.springbootjwt.util.JwtUtils;
  3. import com.auth0.jwt.exceptions.AlgorithmMismatchException;
  4. import com.auth0.jwt.exceptions.InvalidClaimException;
  5. import com.auth0.jwt.exceptions.SignatureVerificationException;
  6. import com.auth0.jwt.exceptions.TokenExpiredException;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import org.springframework.web.servlet.HandlerInterceptor;
  11. import javax.servlet.http.HttpServletRequest;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. /**
  16. * jwt 拦截器
  17. * @author beyond
  18. * @since 2020/12/27
  19. */
  20. public class JWTInterceptor implements HandlerInterceptor {
  21. private static final Logger logger = LoggerFactory.getLogger(JWTInterceptor.class);
  22. @Override
  23. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  24. //获取请求头中的令牌
  25. String token = request.getHeader("token");
  26. logger.info("当前token为:[{}]", token);
  27. Map<String, Object> map = new HashMap<>();
  28. try {
  29. JwtUtils.verify(token);
  30. return true;
  31. } catch (SignatureVerificationException e) {
  32. logger.error("签名不一致", e);
  33. map.put("msg", "签名不一致");
  34. } catch (TokenExpiredException e) {
  35. logger.error("令牌过期", e);
  36. map.put("msg", "令牌过期");
  37. } catch (AlgorithmMismatchException e) {
  38. logger.error("算法不匹配", e);
  39. map.put("msg", "算法不匹配");
  40. } catch (InvalidClaimException e) {
  41. logger.error("失效的payload", e);
  42. map.put("msg", "失效的payload");
  43. } catch (Exception e) {
  44. logger.error("token无效", e);
  45. map.put("msg", "token无效");
  46. }
  47. map.put("state", false);
  48. // 响应到前台,map转为json
  49. String json = new ObjectMapper().writeValueAsString(map);
  50. response.setContentType("application/json;charset=UTF-8");
  51. response.getWriter().println(json);
  52. return false;
  53. }
  54. }

拦截器配置

  1. /**
  2. * 拦截器配置
  3. * @author beyond
  4. * @since 2020/12/27
  5. */
  6. @Configuration
  7. public class InterceptorConfig implements WebMvcConfigurer {
  8. @Override
  9. public void addInterceptors(InterceptorRegistry registry) {
  10. registry.addInterceptor(new JWTInterceptor())
  11. .addPathPatterns("/**") // 所有需要认证的请求
  12. .excludePathPatterns("/user/login") // 不需要认证的请求
  13. ;
  14. }
  15. }

https://juejin.cn/post/7090677150646272036