JWT -- Json Web token
创始人
2024-02-24 11:02:00

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

JWT 由三个部分组成:

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

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

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

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

 认证中心

        org.springframework.bootspring-boot-starter-weborg.projectlomboklombokcom.auth0java-jwt3.8.3

配置:

server:port: 8080jwt: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 handlerTokenUnavailableException(TokenUnavailableException e) {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 {@AutowiredJwtProperties 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 {@Autowiredprivate 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;@Overridepublic 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 {@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {return new RestTemplate(factory);}@Beanpublic ClientHttpRequestFactory simpleClientHttpRequestFactory() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setReadTimeout(5000);factory.setConnectTimeout(15000);return factory;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {//拦截所有请求registry.addInterceptor(jwtAuthenticationInterceptor()).addPathPatterns("/**");}@Beanpublic JwtAuthenticationInterceptor jwtAuthenticationInterceptor() {return new JwtAuthenticationInterceptor();}//自定义处理400,500返回@Beanpublic 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 {@Overridepublic boolean hasError(ClientHttpResponse response) throws IOException {return false;}@Overridepublic 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 {@AutowiredRestTemplate 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的验证。更加简单

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...