JWT请求流程

JWT - 图2

JWT的结构

JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:

  • Header
  • Payload
  • Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)Signature 签名/签证

Header

JWT的头部承载两部分信息:token类型和采用的加密算法(默认就有无需设置)。

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

声明类型:这里是jwt
声明加密的算法:通常直接使用 HMAC SHA256
**

Payload

载荷就是存放有效信息的地方。**

标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: 面向的用户(jwt所面向的用户)
aud: 接收jwt的一方
exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

Signature

jwt的第三部分是一个签证信息
这个部分需要base64加密后的headerbase64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证,所以需要保护好。

总结:
加密:header和payload分别使用base64加密,使用.号连接,再把这个得到的字符串与header中的加密方式进行加盐(盐 自定义secret)加密,就构成第三部分,三个部分全部.用连接起来就是token
验证:拿到字符串(token),获取header和payload,和上面相同的算法加盐加密,得到的字符串再和第三部分的signature进行比较。


SpringBoot集成JWT

需要导入maven

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

JWT工具类

  1. public class JWTUtil {
  2. /**
  3. * 过期时间 15天
  4. */
  5. private static final long EXPIRE_TIME = 15 * 24 * 60 * 60 * 1000;
  6. /**
  7. * token 私钥
  8. */
  9. private static final String TOKEN_SECRET = "07d319d1860c463e8a18b723d35902fc";
  10. /**
  11. * 加密算法
  12. */
  13. private static final Algorithm ALGORITHM = Algorithm.HMAC256(TOKEN_SECRET);
  14. /**
  15. * 生成token15分钟后过期
  16. */
  17. public static String sign(Integer id, String username) {
  18. //过期时间
  19. Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
  20. //生成token 默认header是有数据的 {type:JWT,alg:HS256}
  21. return JWT.create()
  22. .withClaim("id", id) //把id和name存入进去
  23. .withClaim("username", username)
  24. .withExpiresAt(date)
  25. .sign(ALGORITHM);
  26. }
  27. /**
  28. * 校验token是否正确
  29. */
  30. public static DecodedJWT verify(String token) {
  31. return JWT.require(ALGORITHM).build().verify(token);
  32. }
  33. public static Integer getTokenClaimId(String token) {
  34. return JWT.decode(token).getClaim("id").asInt();
  35. }
  36. public static String getTokenClaimUsername(String token) {
  37. return JWT.decode(token).getClaim("username").asString();
  38. }
  39. }

Controller

  1. @RestController
  2. @Slf4j
  3. public class UserController {
  4. @Autowired
  5. private UserService userService;
  6. /**
  7. * 测试登录
  8. * @param loginVo
  9. * @return
  10. */
  11. @PostMapping("/login")
  12. public BaseResult<Object> login(@RequestBody @Valid LoginVo loginVo) {
  13. log.info("loginVo: {}", loginVo);
  14. User currentUser = userService.login(loginVo);
  15. //登录成功 返回token
  16. String token = JWTUtil.sign(currentUser.getId(), currentUser.getUsername());
  17. return BaseResult.success(CommonEnum.SUCCESS, token);
  18. }
  19. /**
  20. * 需要登录才能访问的东西
  21. */
  22. @GetMapping("/admin/info")
  23. @UserLoginToken
  24. public BaseResult<Object> admin() {
  25. return BaseResult.success(CommonEnum.SUCCESS, "已有token 可以成功访问后台");
  26. }
  27. }

拦截器注解:当使用了这个注解 就必须登录才能继续访问

  1. /**
  2. * 拦截器注解:需要登录携带token才能继续访问
  3. */
  4. @Target({ElementType.METHOD, ElementType.TYPE})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface UserLoginToken {
  7. boolean required() default true;
  8. }

拦截器
抛全局异常和response返回数据效果一样

  1. public class VueInterceptor implements HandlerInterceptor {
  2. @Override
  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  4. /**
  5. * 先看有没有注解 没有就放行
  6. * 有注解就验证token的合法性
  7. */
  8. // 如果不是映射到方法直接通过
  9. if (!(handler instanceof HandlerMethod)) {
  10. return true;
  11. }
  12. HandlerMethod hm = (HandlerMethod) handler;
  13. UserLoginToken loginToken = hm.getMethodAnnotation(UserLoginToken.class);
  14. //UserLoginToken注解,有则认证
  15. if (loginToken == null) {
  16. return true;
  17. }
  18. response.setContentType("application/json;charset=utf-8");
  19. String msg = null;
  20. ObjectMapper objectMapper = new ObjectMapper();
  21. //验证token
  22. if (loginToken.required()) {
  23. //获取token
  24. String token = request.getHeader("token");
  25. try {
  26. JWTUtil.verify(token);
  27. return true;
  28. } catch (SignatureVerificationException e) {
  29. msg = "无效签名";
  30. } catch (TokenExpiredException e) {
  31. // msg = "token过期";
  32. throw new GlobalException("已经过期了嘻嘻");
  33. } catch (AlgorithmMismatchException e) {
  34. msg = "token算法不一致";
  35. } catch (Exception e) {
  36. msg = "token无效";
  37. }
  38. } else {
  39. return true;
  40. }
  41. response.getWriter().println(objectMapper.writeValueAsString(BaseResult.error(-1, msg)));
  42. return false;
  43. }
  44. }

注册拦截器

  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addInterceptors(InterceptorRegistry registry) {
  5. registry.addInterceptor(new VueInterceptor())
  6. .addPathPatterns("/**");
  7. }
  8. }

其他不重要的配置

application.yml

  1. debug: true # 打开调试模式
  2. server:
  3. port: 8088
  4. spring:
  5. datasource:
  6. username: root
  7. password: 123456
  8. url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  9. devtools:
  10. restart:
  11. enabled: true
  12. jpa:
  13. show-sql: true

User

  1. @Data
  2. @Accessors(chain = true)
  3. @Entity
  4. @Table(name = "t_user")
  5. public class User {
  6. @Id
  7. @GeneratedValue(strategy = GenerationType.IDENTITY ) //数据库
  8. private Integer id;
  9. private String username;
  10. private String password;
  11. }

LoginVo

  1. @Data
  2. public class LoginVo {
  3. @NotBlank(message = "用户名不能为空")
  4. private String username;
  5. @NotBlank(message = "密码不能为空")
  6. private String password;
  7. }

dao

  1. public interface UserRepository extends JpaRepository<User,Integer> {
  2. /**
  3. * 根据 username ,password查询用户
  4. * @param username
  5. * @param password
  6. * @return
  7. */
  8. User findByUsernameAndPassword(String username,String password);
  9. }

Service

  1. public interface UserService {
  2. User login(LoginVo login);
  3. }

ServiceImpl

  1. @Service
  2. @Transactional
  3. public class UserServiceImpl implements UserService {
  4. @Autowired
  5. private UserRepository userRepository;
  6. //根据用户名密码查询用户
  7. @Override
  8. public User login(LoginVo login) {
  9. User dbUser = userRepository.findByUsernameAndPassword(login.getUsername(), login.getPassword());
  10. if (dbUser == null) {
  11. throw new GlobalException(UserEnum.USER_IS_NULL);
  12. }
  13. return dbUser;
  14. }
  15. }

数据模块枚举

  1. public interface BaseErrorInfoInterface {
  2. /** 错误码*/
  3. Integer getCode();
  4. /** 错误描述*/
  5. String getMessage();
  6. }

CommonEnum

  1. public enum CommonEnum implements BaseErrorInfoInterface {
  2. // 数据操作错误定义
  3. SUCCESS(200, "成功!"),
  4. BODY_NOT_MATCH(400, "请求的数据格式不符!"),
  5. SIGNATURE_NOT_MATCH(401, "请求的数字签名不匹配!"),
  6. NOT_FOUND(404, "未找到该资源!"),
  7. INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
  8. SERVER_BUSY(503, "服务器正忙,请稍后再试!");
  9. /**
  10. * 错误码
  11. */
  12. private Integer code;
  13. /**
  14. * 错误描述
  15. */
  16. private String message;
  17. CommonEnum(Integer code, String message) {
  18. this.code = code;
  19. this.message = message;
  20. }
  21. @Override
  22. public Integer getCode() {
  23. return code;
  24. }
  25. @Override
  26. public String getMessage() {
  27. return message;
  28. }
  29. }

UserEnum

  1. public enum CommonEnum implements BaseErrorInfoInterface {
  2. // 数据操作错误定义
  3. SUCCESS(200, "成功!"),
  4. BODY_NOT_MATCH(400, "请求的数据格式不符!"),
  5. SIGNATURE_NOT_MATCH(401, "请求的数字签名不匹配!"),
  6. NOT_FOUND(404, "未找到该资源!"),
  7. INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
  8. SERVER_BUSY(503, "服务器正忙,请稍后再试!");
  9. /**
  10. * 错误码
  11. */
  12. private Integer code;
  13. /**
  14. * 错误描述
  15. */
  16. private String message;
  17. CommonEnum(Integer code, String message) {
  18. this.code = code;
  19. this.message = message;
  20. }
  21. @Override
  22. public Integer getCode() {
  23. return code;
  24. }
  25. @Override
  26. public String getMessage() {
  27. return message;
  28. }
  29. }

自定义异常

  1. @Getter
  2. @Setter
  3. @NoArgsConstructor
  4. public class GlobalException extends RuntimeException{
  5. private Integer code;
  6. public GlobalException(String message){
  7. super(message);
  8. }
  9. /**
  10. * 枚举父接口 BaseErrorInfoInterface
  11. */
  12. public GlobalException(BaseErrorInfoInterface errorInfoInterface) {
  13. super(errorInfoInterface.getMessage());
  14. this.code = errorInfoInterface.getCode();
  15. }
  16. }

数据返回格式

  1. @Getter
  2. @Setter
  3. @NoArgsConstructor
  4. public class BaseResult<T> {
  5. //返回代码
  6. private Integer code;
  7. //返回消息
  8. private String message;
  9. //返回对象
  10. private T data;
  11. private BaseResult(Integer code, String message) {
  12. this.code = code;
  13. this.message = message;
  14. }
  15. private BaseResult(Integer code, String message, T data) {
  16. this.code = code;
  17. this.message = message;
  18. this.data = data;
  19. }
  20. /**
  21. * 成功
  22. */
  23. public static <T> BaseResult<T> success(Integer code, String message,T data) {
  24. return new BaseResult<T>(code, message,data );
  25. }
  26. public static <T> BaseResult<T> success(BaseErrorInfoInterface base,T data) {
  27. return new BaseResult<T>(base.getCode(), base.getMessage(),data );
  28. }
  29. /**
  30. * 失败
  31. */
  32. public static <T> BaseResult<T> error(Integer code, String message) {
  33. return new BaseResult<T>(code, message);
  34. }
  35. public static <T> BaseResult<T> error(BaseErrorInfoInterface base) {
  36. return new BaseResult<T>(base.getCode(), base.getMessage());
  37. }
  38. }

全局异常处理

  1. @RestControllerAdvice
  2. @Slf4j
  3. public class GlobalExceptionHandler {
  4. /**
  5. * 捕获 自定义的DemoException
  6. */
  7. @ExceptionHandler(GlobalException.class)
  8. public BaseResult<Object> globalExceptionHandler(GlobalException e) {
  9. log.error("GlobalException:{}", e.getMessage());
  10. return BaseResult.error(e.getCode(), e.getMessage());
  11. }
  12. /**
  13. * jsr303 参数校验异常
  14. */
  15. @ExceptionHandler(BindException.class)
  16. public BaseResult<Object> validExceptionHandler(BindException e) {
  17. FieldError fieldError = e.getBindingResult().getFieldError();
  18. if(fieldError!=null){
  19. log.error("参数校验异常:{}({})", fieldError.getDefaultMessage(), fieldError.getField());
  20. // 将错误的参数的详细信息封装到统一的返回实体
  21. return BaseResult.error(-1, fieldError.getDefaultMessage());
  22. }
  23. return null;
  24. }
  25. }