JWT简介

Jwt全称是:json web token。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。

优点:

  1. 简洁: 可以通过URL、POST参数或者在HTTP header发送,因为数据量小,传输速度也很快;
  2. 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
  3. 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
  4. 不需要在服务端保存会话信息,特别适用于分布式微服务。

缺点:

  1. 无法作废已颁布的令牌;
  2. 不易应对数据过期。

备注:JWT官网

1、Jwt消息构成

1.1组成

一个token分3部分,按顺序为

  1. 头部(header)
  2. 载荷(payload)
  3. 签证(signature)

三部分之间用.号做分隔。例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxYzdiY2IzMS02ODFlLTRlZGYtYmU3Yy0wOTlkODAzM2VkY2UiLCJleHAiOjE1Njk3Mjc4OTF9.wweMzyB3tSQK34Jmez36MmC5xpUh15Ni3vOV_SGCzJ8

1.2 header

Jwt的头部承载两部分信息:

  1. 声明类型,这里是Jwt
  2. 声明加密的算法 通常直接使用 HMAC SHA256

Jwt里验证和签名使用的算法列表如下:

JWS 算法名称
HS256 HMAC256
HS384 HMAC384
HS512 HMAC512
RS256 RSA256
RS384 RSA384
RS512 RSA512
ES256 ECDSA256
ES384 ECDSA384
ES512 ECDSA512

1.3 playload

载荷就是存放有效信息的地方。基本上填2种类型数据

  1. 标准中注册的声明的数据;
  2. 自定义数据。

由这2部分内部做base64加密。

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

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

1.4 signature

Jwt的第三部分是一个签证信息,这个签证信息由三部分组成

base64加密后的header和base64加密后的payload连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了Jwt的第三部分。

SpringBoot集成JWT

1、添加依赖

在pom.xml文件中添加如下依赖:

<!-- JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>

2、简单配置使用

2.1创建一个token工具类:

创建TokenUtils.java

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.qiancheng.springboot.entity.User;
import com.qiancheng.springboot.service.IUserService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Component
public class TokenUtils {

private static IUserService staticUserService;

@Resource
private IUserService userService;

/**
* 过期时间30分钟
*/
private static final long EXPIRE_TIME = 30 * 60 * 1000;

@PostConstruct
public void setUserService(){
staticUserService = userService;
}

/**
* 生成Token
* @param userId
* @param sign
* @return
*/
public static String genToken(String userId,String sign) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);

return JWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
.withExpiresAt(date) //30分钟后token过期
.sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥(工作中不能使用password作为密钥)
}

public static User getCurrentUser(){
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
if (StrUtil.isNotBlank(token)){
String userId = JWT.decode(token).getAudience().get(0);
return staticUserService.getById(Integer.valueOf(userId));
}
}catch (Exception e){
return null;
}

return null;
}
}

关于@PostConstruct注解参考文章

2.2创建拦截器,拦截token

创建JWTInterceptor.java

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.qiancheng.springboot.common.Constants;
import com.qiancheng.springboot.entity.User;
import com.qiancheng.springboot.exception.ServiceException;
import com.qiancheng.springboot.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 定义拦截器,拦截token
*/
public class JWTInterceptor implements HandlerInterceptor {

@Autowired
private IUserService userService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
// 执行认证
if (StrUtil.isBlank(token)) {//判断token是不是空字符串
throw new ServiceException(Constants.CODE_401,"无Token,请重新登录!");
}

// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new ServiceException(Constants.CODE_401,"Token验证失败,请重新登录!");
}

//通过userId查询数据库,看用户是否存在
User user = userService.getById(userId);
if (user == null){
throw new ServiceException(Constants.CODE_401,"用户不存在,请重新登录!");
}

// 用户密码加签验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);//验证 token
} catch (JWTVerificationException e) {
throw new ServiceException(Constants.CODE_401,"Token验证失败,请重新登录!");
}

return true;
}

}

2.3注册拦截器

创建InterceptorConfig.java

import com.qiancheng.springboot.config.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**") // 拦截所有请求,通过判断token是否合法,来决定是否需要登录
.excludePathPatterns("/user/login","/user/register","/**/export","/**/import"); ////添加不需要拦截的路径

//注册TestInterceptor拦截器
// InterceptorRegistration registration = registry.addInterceptor(jwtInterceptor());
// registration.addPathPatterns("/**"); //添加拦截路径
// registration.excludePathPatterns( //添加不拦截路径
// "/**/*.html", //html静态资源
// "/**/*.js", //js静态资源
// "/**/*.css", //css静态资源
// "/**/*.woff",
// "/**/*.ttf",
// "/swagger-ui.html"
// );
}

@Bean
public JWTInterceptor jwtInterceptor(){
return new JWTInterceptor();
}

}

2.4使用

在返回前端的数据封装类里新增token属性,登录成功时给token属性赋值,之后前端的所有请求都在header里携带token,后端会自动拦截到token后再处理请求。

返回前端的封装类UserDTO.java:

import lombok.Data;

/**
* 接收前端登录请求参数
*/
@Data
public class UserDTO {

private String username;
private String password;
private String nickname;
private String avatarUrl;

private String token;
}

登录成功,给token赋值:

//获取token
String token = TokenUtils.genToken(one.getId().toString(),one.getPassword());
//设置token
userDTO.setToken(token);