• JWT -- Json Web token


    JWT 的背景知识可以看这篇文章: JSON Web Token 入门教程

    JWT 由三个部分组成:

    • Header(头部)
    • Payload(负载)
    • Signature(签名)

    分布式系统下,存在跨session的问题,则使用JWT实现分布式下,身份验证的功能

    JWT有很多的第三方实现,Java JWT 类库对比及使用 - 简书

    此处我们选择Auth0,此案例实现的功能如下

     认证中心

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-webartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.projectlombokgroupId>
    7. <artifactId>lombokartifactId>
    8. dependency>
    9. <dependency>
    10. <groupId>com.auth0groupId>
    11. <artifactId>java-jwtartifactId>
    12. <version>3.8.3version>
    13. dependency>

    配置:

    1. server:
    2. port: 8080
    3. jwt:
    4. secret: "123321" #私钥
    5. expireTime: 1 #JWT有效期,单位是分钟
    1. import lombok.Data;
    2. import org.springframework.boot.context.properties.ConfigurationProperties;
    3. import org.springframework.stereotype.Component;
    4. @Data
    5. @Component
    6. @ConfigurationProperties(prefix = "jwt")
    7. public class JwtProperties {
    8. private String secret;
    9. private int expireTime;
    10. }

    自定义异常

    1. public class TokenUnavailableException extends RuntimeException{
    2. public TokenUnavailableException(String message) {
    3. super(message);
    4. }
    5. }
    6. import com.lb.bean.JwtResponse;
    7. import org.springframework.http.HttpStatus;
    8. import org.springframework.http.ResponseEntity;
    9. import org.springframework.web.bind.annotation.ExceptionHandler;
    10. import org.springframework.web.bind.annotation.RestControllerAdvice;
    11. @RestControllerAdvice
    12. public class ExceptionAdvice {
    13. @ExceptionHandler(TokenUnavailableException.class)
    14. private ResponseEntity handlerTokenUnavailableException(TokenUnavailableException e) {
    15. return new ResponseEntity<>(JwtResponse.error(e.getMessage()), HttpStatus.BAD_REQUEST);
    16. }
    17. }
    18. JWT - 生成,检验

      1. package com.lb.config;
      2. import com.auth0.jwt.JWT;
      3. import com.auth0.jwt.JWTCreator;
      4. import com.auth0.jwt.JWTVerifier;
      5. import com.auth0.jwt.algorithms.Algorithm;
      6. import com.auth0.jwt.exceptions.TokenExpiredException;
      7. import com.auth0.jwt.interfaces.DecodedJWT;
      8. import com.lb.bean.JwtResponse;
      9. import com.lb.exception.TokenUnavailableException;
      10. import org.springframework.beans.factory.annotation.Autowired;
      11. import org.springframework.stereotype.Component;
      12. import java.time.LocalDateTime;
      13. import java.time.ZoneId;
      14. import java.util.Date;
      15. import java.util.Map;
      16. @Component
      17. public class JwtService {
      18. @Autowired
      19. JwtProperties jwtProperties;
      20. public String createToken(String subject, Map claimMap) {
      21. JWTCreator.Builder builder = JWT.create()
      22. // .withIssuer("")
      23. // .withNotBefore(null)
      24. // .withIssuedAt(null)
      25. // .withAudience("")
      26. .withSubject(subject) //主题
      27. .withExpiresAt(Date.from(
      28. LocalDateTime.now().plusMinutes(jwtProperties.getExpireTime())
      29. .atZone(ZoneId.systemDefault()).toInstant())
      30. );
      31. claimMap.forEach(builder::withClaim);
      32. return builder.sign(Algorithm.HMAC256(jwtProperties.getSecret()));
      33. }
      34. public JwtResponse validateToken(String token) {
      35. try {
      36. JWTVerifier jwtVerifier =
      37. JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
      38. .build();
      39. DecodedJWT decodedJwt = jwtVerifier.verify(token);
      40. return JwtResponse.ok(decodedJwt.getClaims());
      41. } catch (Exception e) {
      42. //校验失败
      43. throw new TokenUnavailableException(e.getMessage());
      44. }
      45. }
      46. public boolean isNeedUpdate(String token) {
      47. try {
      48. Date expiresAt = JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
      49. .build()
      50. .verify(token)
      51. .getExpiresAt();
      52. // 需要更新 时间小于一半
      53. return (expiresAt.getTime() - System.currentTimeMillis()) < ((jwtProperties.getExpireTime() * 60000) >> 1);
      54. } catch (TokenExpiredException e) {
      55. return true;
      56. } catch (Exception e) {
      57. //校验失败
      58. throw new TokenUnavailableException(e.getMessage());
      59. }
      60. }
      61. }

      controller

      1. @RestController
      2. public class JwtController {
      3. @Autowired
      4. private JwtService jwtService;
      5. @PostMapping("create")
      6. public ResponseEntity generateJwtToken(@RequestBody JwtRequest request) {
      7. String token = jwtService.createToken(request.getSubject(), request.getClaimsMap());
      8. return ResponseEntity.ok(token);
      9. }
      10. @PostMapping("validate")
      11. public ResponseEntity validateToken(@RequestParam String token) {
      12. JwtResponse response = jwtService.validateToken(token);
      13. return ResponseEntity.ok(response);
      14. }
      15. @PostMapping("expire")
      16. public ResponseEntity expire(@RequestParam String token) {
      17. return ResponseEntity.ok(jwtService.isNeedUpdate(token));
      18. }
      19. }

      客户端模块

      请求认证中心,生成token,验证token,更新token

      自定义注解,跳过JWT验证的API

      1. import java.lang.annotation.ElementType;
      2. import java.lang.annotation.Retention;
      3. import java.lang.annotation.RetentionPolicy;
      4. import java.lang.annotation.Target;
      5. @Target(ElementType.METHOD)
      6. @Retention(RetentionPolicy.RUNTIME)
      7. public @interface PassToken {
      8. boolean required() default true;
      9. }

      自定义拦截器,验证JWT

      1. package com.lb.interceptor;
      2. import com.fasterxml.jackson.databind.ObjectMapper;
      3. import com.lb.annotation.PassToken;
      4. import com.lb.bean.JwtResponse;
      5. import com.lb.exception.JwtException;
      6. import com.lb.util.JwtHttpEntityUtil;
      7. import org.springframework.http.HttpEntity;
      8. import org.springframework.http.HttpStatus;
      9. import org.springframework.http.ResponseEntity;
      10. import org.springframework.util.StringUtils;
      11. import org.springframework.web.client.RestTemplate;
      12. import org.springframework.web.method.HandlerMethod;
      13. import org.springframework.web.servlet.HandlerInterceptor;
      14. import javax.annotation.Resource;
      15. import javax.servlet.http.HttpServletRequest;
      16. import javax.servlet.http.HttpServletResponse;
      17. import java.lang.reflect.Method;
      18. import java.util.Map;
      19. public class JwtAuthenticationInterceptor implements HandlerInterceptor {
      20. private static ObjectMapper MAPPER = new ObjectMapper();
      21. @Resource(name = "facePlusRestTemplate")
      22. RestTemplate restTemplate;
      23. @Override
      24. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      25. //如果不是映射到方法直接通过
      26. if (!(handler instanceof HandlerMethod)) {
      27. return true;
      28. }
      29. HandlerMethod handlerMethod = (HandlerMethod) handler;
      30. Method method = handlerMethod.getMethod();
      31. //检查时候包含passtoken注解
      32. if (method.isAnnotationPresent(PassToken.class)) {
      33. PassToken passToken = method.getAnnotation(PassToken.class);
      34. if (passToken.required()) {
      35. return true;
      36. }
      37. }
      38. String token = request.getHeader("Authorization");
      39. if (StringUtils.isEmpty(token)) {
      40. throw new JwtException(HttpStatus.UNAUTHORIZED, "token 不存在");
      41. }
      42. HttpEntity formEntity = JwtHttpEntityUtil.getValidateEntity(token);
      43. ResponseEntity stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/validate", formEntity, String.class);
      44. if (stringResponseEntity == null) {
      45. throw new JwtException(HttpStatus.SERVICE_UNAVAILABLE, "服务不可用,请稍后再试");
      46. }
      47. String body = stringResponseEntity.getBody();
      48. JwtResponse jwtRes = MAPPER.readValue(body, JwtResponse.class);
      49. if (!jwtRes.isFlag()) {
      50. throw new JwtException(HttpStatus.UNAUTHORIZED, jwtRes.getMsg());
      51. }
      52. Map claimsMap = jwtRes.getClaimsMap();
      53. request.setAttribute("claims", jwtRes.getClaimsMap());
      54. stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/expire", formEntity, String.class);
      55. if (stringResponseEntity != null && Boolean.parseBoolean(stringResponseEntity.getBody())) {
      56. formEntity = JwtHttpEntityUtil.getCreateTokenEntity(claimsMap.get("sub"), claimsMap.get("pwd"));
      57. token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
      58. response.setHeader("token", token);
      59. }
      60. return true;
      61. }
      62. }

      注册拦截器

      1. import com.lb.exception.FacePlusThrowErrorHandler;
      2. import com.lb.interceptor.JwtAuthenticationInterceptor;
      3. import org.springframework.context.annotation.Bean;
      4. import org.springframework.context.annotation.Configuration;
      5. import org.springframework.http.client.ClientHttpRequestFactory;
      6. import org.springframework.http.client.SimpleClientHttpRequestFactory;
      7. import org.springframework.http.converter.FormHttpMessageConverter;
      8. import org.springframework.http.converter.StringHttpMessageConverter;
      9. import org.springframework.web.client.RestTemplate;
      10. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      11. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      12. import java.nio.charset.StandardCharsets;
      13. @Configuration
      14. public class ApplicationConfig implements WebMvcConfigurer {
      15. @Bean
      16. public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
      17. return new RestTemplate(factory);
      18. }
      19. @Bean
      20. public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
      21. SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
      22. factory.setReadTimeout(5000);
      23. factory.setConnectTimeout(15000);
      24. return factory;
      25. }
      26. @Override
      27. public void addInterceptors(InterceptorRegistry registry) {
      28. //拦截所有请求
      29. registry.addInterceptor(jwtAuthenticationInterceptor()).addPathPatterns("/**");
      30. }
      31. @Bean
      32. public JwtAuthenticationInterceptor jwtAuthenticationInterceptor() {
      33. return new JwtAuthenticationInterceptor();
      34. }
      35. //自定义处理400,500返回
      36. @Bean
      37. public RestTemplate facePlusRestTemplate(ClientHttpRequestFactory factory) {
      38. RestTemplate restTemplate = new RestTemplate(factory);
      39. restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
      40. restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
      41. restTemplate.setErrorHandler(new FacePlusThrowErrorHandler());
      42. return restTemplate;
      43. }
      44. }
      1. import org.springframework.http.client.ClientHttpResponse;
      2. import org.springframework.web.client.ResponseErrorHandler;
      3. import java.io.IOException;
      4. public class FacePlusThrowErrorHandler implements ResponseErrorHandler {
      5. @Override
      6. public boolean hasError(ClientHttpResponse response) throws IOException {
      7. return false;
      8. }
      9. @Override
      10. public void handleError(ClientHttpResponse response) throws IOException {
      11. throw new JwtException(response.getStatusCode(), response.getBody().toString());
      12. }
      13. }
      1. import com.fasterxml.jackson.core.JsonProcessingException;
      2. import com.fasterxml.jackson.databind.ObjectMapper;
      3. import org.springframework.http.HttpEntity;
      4. import org.springframework.http.HttpHeaders;
      5. import org.springframework.http.MediaType;
      6. import org.springframework.util.LinkedMultiValueMap;
      7. import org.springframework.util.MultiValueMap;
      8. import java.util.HashMap;
      9. import java.util.Map;
      10. public class JwtHttpEntityUtil {
      11. private static ObjectMapper MAPPER = new ObjectMapper();
      12. public static HttpEntity getCreateTokenEntity(String userName, String password) throws JsonProcessingException {
      13. HttpHeaders headers = new HttpHeaders();
      14. MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
      15. headers.setContentType(type);
      16. headers.add("Accept", MediaType.APPLICATION_JSON.toString());
      17. HashMap map = new HashMap<>();
      18. map.put("subject", userName);
      19. Map calims = new HashMap<>();
      20. calims.put("pwd", password);
      21. map.put("claimsMap", calims);
      22. String stu = MAPPER.writeValueAsString(map);
      23. return new HttpEntity(stu, headers);
      24. }
      25. public static HttpEntity getValidateEntity(String token) throws JsonProcessingException {
      26. MultiValueMap paramMap = new LinkedMultiValueMap();
      27. paramMap.set("token",token);
      28. //2、添加请求头
      29. HttpHeaders headers = new HttpHeaders();
      30. headers.add("Content-Type","application/x-www-form-urlencoded");
      31. return new HttpEntity(paramMap, headers);
      32. }
      33. }

      客户端Controller 

      1. @RestController
      2. public class MyController {
      3. @Autowired
      4. RestTemplate restTemplate;
      5. @PassToken
      6. @PostMapping("login")
      7. public String login(String userName, String password, HttpServletResponse response) throws JsonProcessingException {
      8. //模拟数据库查询用户
      9. if ("admin".equals(userName) && "admin".equals(password)) {
      10. HttpEntity formEntity = JwtHttpEntityUtil.getCreateTokenEntity(userName, password);
      11. String token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
      12. response.setHeader("token", token);
      13. return "登陆成功";
      14. } else {
      15. return "用户名密码不正确";
      16. }
      17. }
      18. @GetMapping("test")
      19. public String test(HttpServletRequest request) {
      20. return request.getAttribute("claims").toString();
      21. }
      22. }

      以上代码省略了 异常类异常处理类

      结果

       create API -- 拦截器放行login API,并返回token

       

       validate 、expire API -- 验证和更新token

       以上案例是为了,实现注册中心是单独服务,如果不需要它是单独的服务,直接在项目内提供工具类直接生成token,以及token的验证。更加简单

    19. 相关阅读:
      EMQX 实践
      UnityAPI学习之碰撞检测与触发检测
      java实验(头歌)--面向对象封装继承和多态
      Integer和int 的区别
      洛谷P1162 填涂颜色
      电气基础——电源、变压器、接触器、断路器、线缆
      RBF神经网络python实践学习(BP算法)
      一级造价工程师(安装)- 计量笔记 - 重点必考考点
      react评论列表连接数据库
      centos7 安装与卸载 Mysql 5.7.27(详细完整教程)
    20. 原文地址:https://blog.csdn.net/qq_33753147/article/details/128080876