JWT 的背景知识可以看这篇文章: JSON Web Token 入门教程
JWT 由三个部分组成:
在分布式系统下,存在跨session的问题,则使用JWT实现分布式下,身份验证的功能
JWT有很多的第三方实现,Java JWT 类库对比及使用 - 简书,
此处我们选择Auth0,此案例实现的功能如下

- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
-
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- dependency>
-
- <dependency>
- <groupId>com.auth0groupId>
- <artifactId>java-jwtartifactId>
- <version>3.8.3version>
- dependency>
配置:
- server:
- port: 8080
-
- jwt:
- secret: "123321" #私钥
- expireTime: 1 #JWT有效期,单位是分钟
- import lombok.Data;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.stereotype.Component;
-
- @Data
- @Component
- @ConfigurationProperties(prefix = "jwt")
- public class JwtProperties {
- private String secret;
- private int expireTime;
- }
自定义异常
- public class TokenUnavailableException extends RuntimeException{
- public TokenUnavailableException(String message) {
- super(message);
- }
- }
-
- import com.lb.bean.JwtResponse;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.ExceptionHandler;
- import org.springframework.web.bind.annotation.RestControllerAdvice;
-
- @RestControllerAdvice
- public class ExceptionAdvice {
-
- @ExceptionHandler(TokenUnavailableException.class)
- private ResponseEntity
- return new ResponseEntity<>(JwtResponse.error(e.getMessage()), HttpStatus.BAD_REQUEST);
- }
- }
JWT - 生成,检验
- package com.lb.config;
-
- import com.auth0.jwt.JWT;
- import com.auth0.jwt.JWTCreator;
- import com.auth0.jwt.JWTVerifier;
- import com.auth0.jwt.algorithms.Algorithm;
- import com.auth0.jwt.exceptions.TokenExpiredException;
- import com.auth0.jwt.interfaces.DecodedJWT;
- import com.lb.bean.JwtResponse;
- import com.lb.exception.TokenUnavailableException;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
-
- import java.time.LocalDateTime;
- import java.time.ZoneId;
- import java.util.Date;
- import java.util.Map;
-
- @Component
- public class JwtService {
-
- @Autowired
- JwtProperties jwtProperties;
-
- public String createToken(String subject, Map
claimMap) { - JWTCreator.Builder builder = JWT.create()
- // .withIssuer("")
- // .withNotBefore(null)
- // .withIssuedAt(null)
- // .withAudience("")
- .withSubject(subject) //主题
- .withExpiresAt(Date.from(
- LocalDateTime.now().plusMinutes(jwtProperties.getExpireTime())
- .atZone(ZoneId.systemDefault()).toInstant())
- );
- claimMap.forEach(builder::withClaim);
- return builder.sign(Algorithm.HMAC256(jwtProperties.getSecret()));
- }
-
- public JwtResponse validateToken(String token) {
- try {
- JWTVerifier jwtVerifier =
- JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
- .build();
- DecodedJWT decodedJwt = jwtVerifier.verify(token);
-
- return JwtResponse.ok(decodedJwt.getClaims());
- } catch (Exception e) {
- //校验失败
- throw new TokenUnavailableException(e.getMessage());
- }
- }
-
- public boolean isNeedUpdate(String token) {
-
- try {
- Date expiresAt = JWT.require(Algorithm.HMAC256(jwtProperties.getSecret()))
- .build()
- .verify(token)
- .getExpiresAt();
- // 需要更新 时间小于一半
- return (expiresAt.getTime() - System.currentTimeMillis()) < ((jwtProperties.getExpireTime() * 60000) >> 1);
- } catch (TokenExpiredException e) {
- return true;
- } catch (Exception e) {
- //校验失败
- throw new TokenUnavailableException(e.getMessage());
- }
- }
- }
controller
- @RestController
- public class JwtController {
- @Autowired
- private JwtService jwtService;
-
- @PostMapping("create")
- public ResponseEntity
generateJwtToken(@RequestBody JwtRequest request) { - String token = jwtService.createToken(request.getSubject(), request.getClaimsMap());
- return ResponseEntity.ok(token);
- }
-
- @PostMapping("validate")
- public ResponseEntity
validateToken(@RequestParam String token) { - JwtResponse response = jwtService.validateToken(token);
- return ResponseEntity.ok(response);
- }
-
- @PostMapping("expire")
- public ResponseEntity
expire(@RequestParam String token) { - return ResponseEntity.ok(jwtService.isNeedUpdate(token));
- }
- }
请求认证中心,生成token,验证token,更新token
自定义注解,跳过JWT验证的API
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface PassToken {
- boolean required() default true;
- }
自定义拦截器,验证JWT
- package com.lb.interceptor;
-
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.lb.annotation.PassToken;
- import com.lb.bean.JwtResponse;
- import com.lb.exception.JwtException;
- import com.lb.util.JwtHttpEntityUtil;
- import org.springframework.http.HttpEntity;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.util.StringUtils;
- import org.springframework.web.client.RestTemplate;
- import org.springframework.web.method.HandlerMethod;
- import org.springframework.web.servlet.HandlerInterceptor;
-
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.lang.reflect.Method;
- import java.util.Map;
-
-
- public class JwtAuthenticationInterceptor implements HandlerInterceptor {
- private static ObjectMapper MAPPER = new ObjectMapper();
- @Resource(name = "facePlusRestTemplate")
- RestTemplate restTemplate;
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- //如果不是映射到方法直接通过
- if (!(handler instanceof HandlerMethod)) {
- return true;
- }
- HandlerMethod handlerMethod = (HandlerMethod) handler;
- Method method = handlerMethod.getMethod();
- //检查时候包含passtoken注解
- if (method.isAnnotationPresent(PassToken.class)) {
- PassToken passToken = method.getAnnotation(PassToken.class);
- if (passToken.required()) {
- return true;
- }
- }
- String token = request.getHeader("Authorization");
- if (StringUtils.isEmpty(token)) {
- throw new JwtException(HttpStatus.UNAUTHORIZED, "token 不存在");
- }
- HttpEntity formEntity = JwtHttpEntityUtil.getValidateEntity(token);
- ResponseEntity
stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/validate", formEntity, String.class); - if (stringResponseEntity == null) {
- throw new JwtException(HttpStatus.SERVICE_UNAVAILABLE, "服务不可用,请稍后再试");
- }
- String body = stringResponseEntity.getBody();
- JwtResponse jwtRes = MAPPER.readValue(body, JwtResponse.class);
- if (!jwtRes.isFlag()) {
- throw new JwtException(HttpStatus.UNAUTHORIZED, jwtRes.getMsg());
- }
- Map
claimsMap = jwtRes.getClaimsMap(); - request.setAttribute("claims", jwtRes.getClaimsMap());
-
- stringResponseEntity = restTemplate.postForEntity("http://localhost:8080/expire", formEntity, String.class);
- if (stringResponseEntity != null && Boolean.parseBoolean(stringResponseEntity.getBody())) {
- formEntity = JwtHttpEntityUtil.getCreateTokenEntity(claimsMap.get("sub"), claimsMap.get("pwd"));
- token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
- response.setHeader("token", token);
- }
- return true;
- }
- }
注册拦截器
- import com.lb.exception.FacePlusThrowErrorHandler;
- import com.lb.interceptor.JwtAuthenticationInterceptor;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.client.ClientHttpRequestFactory;
- import org.springframework.http.client.SimpleClientHttpRequestFactory;
- import org.springframework.http.converter.FormHttpMessageConverter;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.web.client.RestTemplate;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
- import java.nio.charset.StandardCharsets;
-
- @Configuration
- public class ApplicationConfig implements WebMvcConfigurer {
- @Bean
- public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
- return new RestTemplate(factory);
- }
-
- @Bean
- public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
- SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
- factory.setReadTimeout(5000);
- factory.setConnectTimeout(15000);
- return factory;
- }
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- //拦截所有请求
- registry.addInterceptor(jwtAuthenticationInterceptor()).addPathPatterns("/**");
- }
-
- @Bean
- public JwtAuthenticationInterceptor jwtAuthenticationInterceptor() {
- return new JwtAuthenticationInterceptor();
- }
-
- //自定义处理400,500返回
- @Bean
- public RestTemplate facePlusRestTemplate(ClientHttpRequestFactory factory) {
- RestTemplate restTemplate = new RestTemplate(factory);
- restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
- restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
- restTemplate.setErrorHandler(new FacePlusThrowErrorHandler());
- return restTemplate;
- }
- }
- import org.springframework.http.client.ClientHttpResponse;
- import org.springframework.web.client.ResponseErrorHandler;
-
- import java.io.IOException;
-
- public class FacePlusThrowErrorHandler implements ResponseErrorHandler {
- @Override
- public boolean hasError(ClientHttpResponse response) throws IOException {
- return false;
- }
-
- @Override
- public void handleError(ClientHttpResponse response) throws IOException {
- throw new JwtException(response.getStatusCode(), response.getBody().toString());
- }
- }
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import org.springframework.http.HttpEntity;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.MediaType;
- import org.springframework.util.LinkedMultiValueMap;
- import org.springframework.util.MultiValueMap;
-
- import java.util.HashMap;
- import java.util.Map;
-
- public class JwtHttpEntityUtil {
- private static ObjectMapper MAPPER = new ObjectMapper();
- public static HttpEntity
getCreateTokenEntity(String userName, String password) throws JsonProcessingException { - HttpHeaders headers = new HttpHeaders();
- MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
- headers.setContentType(type);
- headers.add("Accept", MediaType.APPLICATION_JSON.toString());
- HashMap
map = new HashMap<>(); - map.put("subject", userName);
- Map
calims = new HashMap<>(); - calims.put("pwd", password);
- map.put("claimsMap", calims);
- String stu = MAPPER.writeValueAsString(map);
- return new HttpEntity
(stu, headers); - }
-
- public static HttpEntity getValidateEntity(String token) throws JsonProcessingException {
- MultiValueMap
paramMap = new LinkedMultiValueMap(); - paramMap.set("token",token);
- //2、添加请求头
- HttpHeaders headers = new HttpHeaders();
- headers.add("Content-Type","application/x-www-form-urlencoded");
- return new HttpEntity(paramMap, headers);
- }
- }
客户端Controller
- @RestController
- public class MyController {
-
- @Autowired
- RestTemplate restTemplate;
-
- @PassToken
- @PostMapping("login")
- public String login(String userName, String password, HttpServletResponse response) throws JsonProcessingException {
- //模拟数据库查询用户
- if ("admin".equals(userName) && "admin".equals(password)) {
- HttpEntity
formEntity = JwtHttpEntityUtil.getCreateTokenEntity(userName, password); - String token = restTemplate.postForObject("http://localhost:8080/create", formEntity, String.class);
- response.setHeader("token", token);
- return "登陆成功";
- } else {
- return "用户名密码不正确";
- }
- }
-
- @GetMapping("test")
- public String test(HttpServletRequest request) {
- return request.getAttribute("claims").toString();
- }
- }
以上代码省略了 异常类和异常处理类
create API -- 拦截器放行login API,并返回token
validate 、expire API -- 验证和更新token

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