JJWT:Java json web token ,就是基于Java实现的JWT。
首先说一下什么是JWT?
其实就是一个字符串:由三部分组成,头部、载荷与签名。
头部:(header)
一般放一些声明信息,比如:用什么加密,用什么编码。
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。
这也可以被表示成一个JSON对象:
{"typ":"JWT","alg":"HS256"}
使用base64进行编码之后:(至于为什么要用base64,这个我也不知道,好像jwt默认就是用base64编码的)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
这里面放的是主要的有用的信息;
比如说:登陆的用户名,登录时间,登录的过期时间,
还可以放一些自己自定义的一些信息,不限制。
一般载荷这块个性化比较多。
载荷就是存放有效信息的地方,这些有效信息包含三个部分:
(1)标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户 //这个以后就是放我们登录的用户名
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间 //过期时间也可以放
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
但不建议添加敏感信息,因为该部分在客户端可解密。
(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,
因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。
定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64编码,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签名(signature)
就是把头部和载荷加到一起,进行加密(加密之前先加个盐进去)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret (盐)
这个部分需要base64加密后的header和base64加密后的payload使用。
连接组成的字符串,
然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:这里面有几个要注意的地方:
secret 是保存在服务器端的,jwt的签发生成也是在服务器端的。
secret 就是用来进行 jwt 的签发和 jwt 的验证。
所以,它是服务器端的私钥,在任何场景都不应该流露出去,不能被泄露。
一但客户端知道了这个secret ,那就意味着客户端可以自我签发 jwt 了。
因此要注意 secret 的保密安全性。
(有的公司隔一段时间换一次,不同的微服务使用不同的 secret ,因此最好写在配置文件中,方便修改)
使用步骤:
pom引入依赖:
io.jsonwebtoken
jjwt
0.6.0
demo:
生成token方法:
public static void main(String[] args) {
String secretKey = "123";
JwtBuilder jwtBuilder = Jwts.builder()
.setId("666")
.setSubject("小马")
.setIssuedAt(new Date())//登录时间-也就是签发时间(签发给你token的时间)
.setExpiration(new Date(new Date().getTime() + (60 * 1000)))//设置过期时间-一分钟
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())//设置签名秘钥
/** token添加自定义属性 **/
.claim("role", "admin");//设置用户角色
System.out.println(jwtBuilder.compact());
}
解析 jwt :
public static void main(String[] args) {
String secretKey = "123";
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiLlsI_pqawiLCJpYXQiOjE1NDg5MTYxMDQsImV4cCI6MTU0ODkxNjE2NCwicm9sZSI6ImFkbWluIn0.kvuuavo0VoZkvgz8naQ13BQ7uIXjNs0A23I_Wi5AbLk";
Claims claims = Jwts.parser()
.setSigningKey(secretKey.getBytes())//设置对应的盐
.parseClaimsJws(token)
.getBody();
//Claims 这个claims你就可以认为它是一个map,里面是一个个key-value
System.out.println("用户id:" + claims.getId());
System.out.println("用户名称:" + claims.getSubject());
System.out.println("登录时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(claims.getIssuedAt()));
System.out.println("过期时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(claims.getExpiration()));
System.out.println("用户角色:" + claims.get("role"));
//如果过期的token请求过来,它是直接报错的,
//所以一般我们解析token这块,会加上try-catch
}
业务中如何使用呢?
一般我们会把token的验证放在拦截器中。
1:创建拦截器类
@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception {
System.out.println("经过了拦截器");
return true;
}
}
2:配置拦截器类
@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor).
addPathPatterns("/**").
excludePathPatterns("/**/login");
}
}
3:最后在具体实现自己的 拦截器方法:
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception{
if(!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod= (HandlerMethod) handler;
HttpRequestInfo requestInfo = new HttpRequestInfo(request);
String url = request.getRequestURL().toString();
String tokenHeader = request.getHeader("Authorization");
.......
.......
//做一些自己的检验
}