1、什么是 JWT?
JSON Web Token,通过数字签名的方式,以 JSON 对象为载体,在不同的服务终端之间安全的传输信息。
2、JWT 有什么用?
JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求的之前,都要先进行 JWT 安全校验,通过之后再进行处理。
3、JWT 的组成
JWT 由 3 部分组成,用.拼接
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlRvbSIsInJvbGUiOiJhZG1pbiIsInN1YiI6ImFkbWluLXRlc3QiLCJleHAiOjE2MjMyMjM2NzUsImp0aSI6ImQ2MTJjZjcxLWI5ZmUtNGMwNy04MzQwLTViOWViZmMyNjExNyJ9.FOS9Y7rYNdc2AOidnSPrgg2XTYePU0yGZ598h2gtabE
这三部分分别是:
- Header
{ 'typ': 'JWT', 'alg': 'HS256' }
- Payload
{ "sub": '1234567890', "name": 'xmp', "admin":true }
- Signature
varencodedString=base64UrlEncode(header) +'.'+base64UrlEncode(payload); varsignature=HMACSHA256(encodedString, 'secret');
4、优缺点
优点
- 简洁: 可以通过URL、POST参数或者在HTTP header发送,因为数据量小,传输速度也很快;
- 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
缺点
- 无法作废已颁布的令牌;
- 不易应对数据过期。
5、测试
pom.xml jwt依赖坐标
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.13.0</version></dependency>
token登录,浏览器f12可以观察到token信息
6、SpringBoot集成JWT实现token验证
1、pom.xml
jwt坐标
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.13.0</version><!--版本号可以根据自己的--></dependency>
工具类坐标
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.20</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency>
2、代码
生成token
packagecom.example.boot.utils; importcn.hutool.core.date.DateUtil; importcn.hutool.core.util.StrUtil; importcom.auth0.jwt.JWT; importcom.auth0.jwt.algorithms.Algorithm; importcom.example.boot.entity.User; importcom.example.boot.service.IUserService; importorg.springframework.stereotype.Component; importorg.springframework.web.context.request.RequestContextHolder; importorg.springframework.web.context.request.ServletRequestAttributes; importjavax.annotation.PostConstruct; importjavax.annotation.Resource; importjavax.servlet.http.HttpServletRequest; importjava.util.Date; publicclassTokenUtils { privatestaticIUserServicestaticUserService; privateIUserServiceuserService; publicvoidsetUserService() { staticUserService=userService; } /*** 生成token* author: xmp* date: 2022/5/12* @return*/publicstaticStringgenToken(StringuserId, Stringsign) { returnJWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷 .withExpiresAt(DateUtil.offsetHour(newDate(), 2)) // 2小时后token过期 ===>当前日期加2小时 .sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥 } /*** 获取当前登录的用户信息** @return user对象*/publicstaticUsergetCurrentUser() { try { HttpServletRequestrequest= ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Stringtoken=request.getHeader("token"); if (StrUtil.isNotBlank(token)) { StringuserId=JWT.decode(token).getAudience().get(0); returnstaticUserService.getById(Integer.valueOf(userId)); } } catch (Exceptione) { returnnull; } returnnull; } }
jwt
packagecom.example.boot.config.interceptor; importcn.hutool.core.util.StrUtil; importcom.auth0.jwt.JWT; importcom.auth0.jwt.JWTVerifier; importcom.auth0.jwt.algorithms.Algorithm; importcom.auth0.jwt.exceptions.JWTDecodeException; importcom.auth0.jwt.exceptions.JWTVerificationException; importcom.example.boot.common.Constants; importcom.example.boot.entity.User; importcom.example.boot.exception.ServiceException; importcom.example.boot.service.IUserService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.web.method.HandlerMethod; importorg.springframework.web.servlet.HandlerInterceptor; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; publicclassJwtInterceptorimplementsHandlerInterceptor { privateIUserServiceuserService; publicbooleanpreHandle(HttpServletRequestrequest, HttpServletResponseresponse, Objecthandler) { Stringtoken=request.getHeader("token"); // 如果不是映射到方法直接通过if(!(handlerinstanceofHandlerMethod)){ returntrue; } // 执行认证if (StrUtil.isBlank(token)) { thrownewServiceException(Constants.CODE_401, "无token,请重新登录"); } // 获取 token 中的 user idStringuserId; try { userId=JWT.decode(token).getAudience().get(0); } catch (JWTDecodeExceptionj) { thrownewServiceException(Constants.CODE_401, "token验证失败,请重新登录"); } // 根据token中的userid查询数据库Useruser=userService.getById(userId); if (user==null) { thrownewServiceException(Constants.CODE_401, "用户不存在,请重新登录"); } // 用户密码加签验证 tokenJWTVerifierjwtVerifier=JWT.require(Algorithm.HMAC256(user.getPassword())).build(); try { jwtVerifier.verify(token); // 验证token } catch (JWTVerificationExceptione) { thrownewServiceException(Constants.CODE_401, "token验证失败,请重新登录"); } returntrue; } }
拦截,通过判断token是否合法来决定是否需要登录
packagecom.example.boot.config; importcom.example.boot.config.interceptor.JwtInterceptor; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.web.servlet.config.annotation.InterceptorRegistry; importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer; publicclassInterceptorConfigimplementsWebMvcConfigurer { publicvoidaddInterceptors(InterceptorRegistryregistry) { registry.addInterceptor(jwtInterceptor()) .addPathPatterns("/**") // 拦截所有请求,通过判断token是否合法来决定是否需要登录 .excludePathPatterns("/user/login", "/user/register", "/**/export", "/**/import"); } publicJwtInterceptorjwtInterceptor() { returnnewJwtInterceptor(); } }
service层,接口实现类的登录逻辑
publicUserDtologin(UserDtouserDTO) { Userone=getUserInfo(userDTO); if (one!=null) { BeanUtil.copyProperties(one, userDTO, true); //设置tokenStringtoken=TokenUtils.genToken(one.getId().toString(), one.getPassword()); userDTO.setToken(token); returnuserDTO; } else { thrownewServiceException(Constants.CODE_600, "用户名或密码错误"); } }