JWT
JSON Web令牌简介
JSON Web Token(JWT)是一个开放标准,用于在各方之间以JSON对象安全的传输对象。
JWT能做什么?
- 授权
这是JWT最常见的方式。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它开销小并且可以在不同的域中使用
- 信息交换
在不同的数据之间进行信息交换,因为可以对JWT进行签名,所以可以确保发件人是想要的人。并且通过签名还可以判断内容是否被篡改。
传统Session认证
暴露的问题:
- 每个用户认证之后都会在服务器做一次记录,方便用户下次请求的鉴别,而session都是保存在内存中,随着用户增多,服务器开销会增大。
- 用户认证之后,session记录被保存在服务器的内存中,就意味着用户下次请求还要在这个服务器上,如果在分布式的应用上就限制了负载均衡的能力,限制了应用的扩展能力。
- 因为是基于cookie进行认证的,cookie如果被截获,服务器就是容易受到跨站请求伪造(CSRF)的攻击。
基于JWT认证
JWT结构
令牌组成
- 标头(header)
- 有效载荷(payload)
- 签名(signature)
因此。JWT(token)经常显示为:xxxx.yyyy.zzzz 一段字符串
Header
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法(因为JWT第三部分就是进行签名),例如HMAC、SHA526或RSA。然后使用Base64编码组成JWT结构的第一部分(注意:Base64是一种编码,也就是说,它可以被翻译回以前的样子,并不是一种加密过程)。
示例(头部的默认值也是这个)
{
"alg": "HS256",
"typ": "JWT"
}
Payload
有效载荷是有关实体(通常是用户)和其他数据的生命,同样会使用Base64编码。(注意:此部分不要放置用户敏感信息,如密码,否则会导致信息安全 问题)
示例
{
"sub": "123456",
"name": "zhangsan",
"admin": true
}
Signature
签名是编码后的第一部分+编码后的第二部分和我们提供的一个秘钥,然后使用头部规定的签名算法进行签名。签名的目的是防止头部信息被篡改。
如:HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
JWT的第一个程序
- 引入依赖
<!--引入jwt依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
- 生成token
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 600);
String token = JWT.create()// header有默认值可不用写
.withClaim("userId", 21)//payload
.withClaim("username", "zhangsan")
.withExpiresAt(calendar.getTime())// token过期时间 600秒后过期
.sign(Algorithm.HMAC256("!@sdsfsdgfdg")); // 签名
System.out.println(token);
// 结果
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTYyMTU1MzksInVzZXJJZCI6MjEsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.Y8CrFynod_hd4983yk9azl8xXIB5YiwajFSE_B71qAk
- 获取token中的数据
//创建验证对象
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("!@sdsfsdgfdg")).build();// 注意秘钥要和生成token的秘钥一致
DecodedJWT verify = verifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTYyMTU1MzksInVzZXJJZCI6MjEsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.Y8CrFynod_hd4983yk9azl8xXIB5YiwajFSE_B71qAk");
System.out.println(verify.getClaim("userId").asInt());
System.out.println(verify.getClaim("username").asString());
// 测试结果
21
zhangsan
JWT常见异常
封装工具类
public class JWTUtil {
public static final String secret = "!sdsfsdgfdg";
//获取token
public static String getToken(Map<String, String> map){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, 7);// 默认7天过期
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v) -> builder.withClaim(k,v));
String token = builder.withExpiresAt(calendar.getTime())
.sign(Algorithm.HMAC256(secret));
return token;
}
//验证合法性并获取参数
public static DecodedJWT getDDecodedJWT(String token){
return JWT.require(Algorithm.HMAC256(secret)).build().verify(token);//若报错 则说明token不合法
}
}
拦截器使用
public class JWTIntercepeptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String, Object> map = new HashMap<>();
//获取请求头中的令牌
String token = request.getHeader("token");
try {
JWTUtil.getDDecodedJWT(token);//验证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
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTIntercepeptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/**");
}
}