通过JSON的格式作为web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程可以完成数据加密,签名等相关处理。
JWT能做什么
1、授权
一旦用户开始登陆,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登陆是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中使用。
2、信息交换
JWT是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名,所以可以保证发件人是所希望的。此外,由于签名是使用标头和有效负载计算的,还可以验证内容是否遭到篡改!
JWT的优势是什么?
1、简洁:可以通过URL,POST参数或者在Http header发送,数据量小,传输速度快
2、自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库
3、因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
4、不需要在服务端保存会话信息,特别适合于分布式微服务!
JWT的结构是什么?
令牌组成:1、标头(Header)2、有效载荷(PayLoad)3、签名(Signature)
形式是 header.payload.singnature
Header
标头通常由两部分组成:令牌的类型(即JWT)和所使用的的签名算法。例如HMAC、SHA256或者RSA。它会使用Base64编码组成JWT结构的第一部分。
注意:Base64是一种编码,也就是说,它可以被翻译为原来的样子,不是一种加密过程。
{
"alg":"HS256",
"typ":"JWT"
}
Base64 xxxxx.
Payload
有效负载,其中包含声明,声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分。
{
"sub":"123456789",
"name":"John Doe",
"admin":ture
}
Base64 .yyyyyy.
Signature
前面两部分都是使用Base64进行编码的,即前端可以解开里面的信息。Signature需要使用编码后的header 和 payload以及我们提供的一个密钥,然后使用header指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过!
签名目的:最后一步签名的过程,实际上是头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载内容解码之后再进行修改,在进行编码,最后加上之前的签名组合成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
信息安全问题:不应该在负载里面加入敏感信息。
第一个JWT案例代码
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;
import java.util.Calendar;
import java.util.Date;
public class TestToken {
//令牌获取
@Test
void getToken(){
Calendar instance=Calendar.getInstance();
instance.add(Calendar.SECOND,200);//20秒
String token=JWT.create()
.withClaim("userId","123")
.withClaim("username","zidu")
.withExpiresAt(instance.getTime())//指定令牌过期时间
.sign(Algorithm.HMAC256("!@#@#R#DWE!@")); //签名
System.out.println(token);
}
//令牌校验
@Test
public void checkToken(){
//根据上面的私钥生成一个验签对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!@#@#R#DWE!@")).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjEwNjg4NzAsInVzZXJJZCI6IjEyMyIsInVzZXJuYW1lIjoiemlkdSJ9.VxoFYsUKCCNpq7_Hqz0mTagXaOMV74ttRoxh5W21tQM");
Date expiresAt = verify.getExpiresAt();//获取令牌失效时间
System.out.println(expiresAt);
System.out.println(verify.getClaim("userId").asString());
System.out.println(verify.getClaim("username").asString());
}
}
===================================================================================
//常见异常
签名异常
令牌过期异常
算法不匹配异常
失效的PayLoad异常
封装JWT工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/*
* @ author Selfcrossing
* */
public class JWTUtil {
//服务器上的私钥
private static final String SIGN="!@#@#R#DWE!@";
/*
* 生成token heade.payload.signature
*/
public static String getToken(Map<String,String>map){
Calendar instance=Calendar.getInstance();
instance.add(Calendar.DATE,7);//默认七天过期
JWTCreator.Builder builder=JWT.create();
// 设置要在payLoad中存放的键值对
map.forEach((k,v)->{
builder.withClaim(k,v);
});
//设置过期时间 & 加密算法
String token=builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SIGN));
return token;
}
/*
* 验证token,没有异常即验证通过
*
* */
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
}
测试
/*
*
* 用户登录
* */
@GetMapping("login")
@ResponseBody
public Map<String,Object> login(String username,String password){
System.out.println(username);
System.out.println(password);
Map<String,Object> map=new HashMap<>();
User user=new User(username,password);
User userDB=userService.login(user);
if (userDB!=null)
{
HashMap<String,String> payload=new HashMap<>();
payload.put("id",userDB.getId());
payload.put("username",userDB.getUsername());
//生成JWT令牌
String token = JWTUtil.getToken(payload);
map.put("state",true);
map.put("msg","登陆成功");
map.put("token",token); //响应token
}
else
{
map.put("state", false);
map.put("msg", "登录失败");
}
return map;
}
======================================================================================
************************************无情分割线*****************************************
======================================================================================
@PostMapping("test")
@ResponseBody
public Map<String, Object> test01(String token){
System.out.println(token);
Map<String,Object> map=new HashMap<>();
try
{
//验证令牌
DecodedJWT verify = JWTUtil.verify(token);
map.put("state",true);
map.put("msg","登陆成功");
return map;
}
//签名不一致异常
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","算法不一致!");
}
//
catch (Exception e)
{
e.printStackTrace();
map.put("msg","无效签名!!");
}
map.put("state", false);
return map;
}
上面这个例子,我们可以拿着所有需要登录以后才能操作的接口,利用token进行验证保护。但是如果每次都要传入一个token,并且在每个接口里面都要去验证到底准确不准确,那是非常麻烦的。于是我们可以利用拦截器!
解决方法
在单体下面,用拦截器去做,在分布式系统里面,利用网关去解决!
package com.zidu.travels.interceptors;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zidu.travels.utils.JWTUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/*
* 令牌拦截器
* */
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取请求头中的令牌
String token = request.getHeader("token");
Map<String,Object> map=new HashMap<>();
try
{
//验证令牌
DecodedJWT verify = JWTUtil.verify(token);
map.put("state",true);
map.put("msg","登陆成功");
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","算法不一致!");
}
//
catch (Exception e)
{
e.printStackTrace();
map.put("msg","无效签名!!");
}
map.put("state", false);//设置状态
//将map转为json
String json=new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
配置拦截器!
package com.zidu.travels.config;
import com.zidu.travels.interceptors.JWTInterceptor;
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(new JWTInterceptor())
.addPathPatterns("/user/test")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/register");
}
}