JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。
JWT的认证流程如下:
1、首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
2、后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串 3、后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
4、前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
5、后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
6、验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
JWT:JSON Web Token,token就是一段字符串,由三部分组成:Header,Payload,Signature
Header
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
{ "alg": "HS256", "typ": "JWT" }
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人 exp:到期时间 sub:主题 aud:用户 nbf:在此之前不可用 iat:发布时间 jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{ "userId": "1", "userAccount": "wupengyu", }
Signature
一段签名数据
类似于:
SING="ahfuau&@$&@$@%";
1、导入maven依赖
这里使用的时auth0中的jwt,hutool中也有jwt,此处不再过多研究
com.auth0
java-jwt
3.4.0
2、编写JWTUtils工具包
此处可以作为通用JWT工具类
public class JWTUtils {
private static final String SING = "!Q@W3e4r%T^Y";
/**
* 生成token header.payload.sing
* */
public static String getToken(Map map){
//获取当前时间
Calendar instance = Calendar.getInstance();
//Calendar中add,对天数进行操作,设置当前时间后七天
instance.add(Calendar.DATE,7);
//创建JWT builder
JWTCreator.Builder builder = JWT.create();
//设置payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder
.withExpiresAt(instance.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256(SING));
System.out.println(token);
return token;
}
/**
* 验证token 合法性
* */
public static void verify(String token){
//验签算法,要与签名算法相同
JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
/**
* 获取token信息
* */
public static DecodedJWT getTokenInfo(String token){
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
//从DecodedJWT中拿token的信息
return decodedJWT;
}
}
3、编写token的拦截器
如果在每个接口处都对token进行校验,会造成代码冗余,所以我们写一个拦截器或者AOP,如果是分布式系统,那就将此操作写进网关。
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map map = new HashMap<>();
//获取请求头中的令牌
String token = request.getHeader("token");
try{
JWTUtils.verify(token);//验证令牌
return true;//请求放行
}catch (SignatureVerificationException e){
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
}catch (AlgorithmMismatchException e){
e.printStackTrace();
map.put("msg","token算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("state",false);
//将map转化为json
Gson gson = new Gson();
String json = gson.toJson(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(json);
return false;
}
}
4、配置token拦截器
自主选择不需要拦截的路径进行配置即可
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/login/byPhone")
.excludePathPatterns("/user/sendMsg");
}
}
5、之后,在登录接口中,用户登陆成功后,生成一个token,并作为响应值返回给前端,前端将其设置到请求头中。如需使用token中的用户信息,调用token的解析方法就可以。
扩展:如果想注销时删除token,除了在前端删除请求头外,还应设置黑名单,可以使用redis设置黑名单,防止恶意使用还未过期的token进行数据访问。