什么是jwt?
jwt (json web token),是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
jwt结构
jwt 有三部分组成:头Header,有效载荷Payload,签名Signature;
头Header
header典型的由两部分组成:token的类型(“JWT”)和算法名称
{
'alg': "HS256",
'typ': "JWT"
}
然后,用Base64对这个JSON编码就得到JWT的第一部分.
有效载荷Payload
它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。
- Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
- Public claims : 可以随意定义。
- Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
{
"sub": '12345',
"name": 'user',
"admin":true
}
注册的声明
iss: jwt签发者
sub: 面向的用户(jwt所面向的用户)
aud: 接收jwt的一方
exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
对payload进行Base64编码就得到JWT的第二部分
注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
签名Signature
为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。
jwt流程图:
https://jwt.io/#libraries-io官网地址:https://jwt.io/#libraries-io
springboot2.x集成jwt
pom.xml添加jwt依赖
<!--jwt (json web token) token 生成 校验-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成和解析token的工具类
1.对称加密算法
/**
* json web token
* 生成和解析token的工具类
* 对称加密算法
*
*/
public class JwtTokenUtils {
SecretKeySpec key;
/**
*
* @param key
* 密钥
*/
public JwtTokenProvider(String key){
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),
SignatureAlgorithm.HS512.getJcaName());
this.key=secretKeySpec;
}
/**
* 生成token
* @return
*/
public String createToken(Claims claims){
return Jwts.builder()
.setHeaderParam("type","jwt")
// .compressWith(CompressionCodecs.DEFLATE) //内容压缩
.setClaims(claims) //jwt 主体内容
.signWith(SignatureAlgorithm.HS512,key) //签名方式
.compact(); //生成
}
/**
*校验token
* @param token
* @return
*/
public Claims parseToken(String token){
try {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
return null;
}
}
/**
* Claims 初始化token有效时间 秒
* 7天 单位秒 604800
* @param expire 初始化token有效时间 秒
* @return
*/
public DefaultClaims getDefaultClaims(long expire) {
Claims claims = initClaims(new DefaultClaims(), expire);
return (DefaultClaims) claims;
}
/**
* 设置token有效期
* @param claims
* @param expire
* @return
*/
public Claims setClaimsExpire(Claims claims,long expire) {
return initClaims(claims, expire);
}
public Claims initClaims(Claims claims, long expire) {
if (claims!=null && expire>0) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime()+expire*1000);
claims.setIssuedAt(nowDate);
claims.setExpiration(expireDate);
}
return claims;
}
}
2.非对称加密算法
采用RSA算法
/**
* RSA 算法
*/
public class RSAUtils {
/**
* 获取公钥
* @param publicKey
* @return
* JDK
* java.security.spec.X509EncodedKeySpec
* java.security.KeyFactory
*/
public static PublicKey getPublicKey(byte[] publicKey) {
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = null;
try {
kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取私钥
* @param privateKey
* @return
* JDK
* java.security.spec.PKCS8EncodedKeySpec
* java.security.KeyFactory
*/
public static PrivateKey getPrivateKey(byte[] privateKey){
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKey);
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
/**
* 生成公私钥
* byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
* byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
* String publicKey = Base64.getEncoder().encodeToString(publicKeyBytes)
*/
public static KeyPair generateKey(){
try {
KeyPairGenerator keyPairGenerator= KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom();
keyPairGenerator.initialize(1024,secureRandom);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
RSAUtils 工具生成公私钥对
/**
* 生成和解析token的工具类【非对称加密算法版】
*
*
*/
public class JwtToken2Utils {
PublicKey publicKey;
PrivateKey privateKey;
public JwtToken2Utils (String publicKey,String privateKey){
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKey);
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKey);
try {
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory publicKeyFactory = KeyFactory.getInstance("RSA");
this.publicKey = publicKeyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory privateKeyFactory = KeyFactory.getInstance("RSA");
this.privateKey = privateKeyFactory.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
}
public JwtToken2Utils (KeyPair keyPair){
this.publicKey = keyPair.getPublic();
this.privateKey =keyPair.getPrivate();
}
/**
* 生成token
* @param claims
* @return
*/
public String createToken(Claims claims){
return Jwts.builder()
.setHeaderParam("type","jwt")
.setClaims(claims) //claims,就是自定义的playload部分
// .compressWith(CompressionCodecs.DEFLATE) //内容压缩
.signWith(SignatureAlgorithm.RS512,privateKey) //签名用私钥
.compact();
}
/**
*
* @param token
* @return
*/
public Claims parseToken(String token){
Claims claims = Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
return claims;
}
/**
* Claims 初始化token有效时间 秒
* 7天 单位秒 604800
* @param expire
* @return
*/
public DefaultClaims getDefaultClaims(long expire) {
Claims claims = initClaims(new DefaultClaims(), expire);
return (DefaultClaims) claims;
}
/**
* 设置token有效期
* @param claims
* @param expire
* @return
*/
public Claims setClaimsExpire(Claims claims,long expire) {
return initClaims(claims, expire);
}
public Claims initClaims(Claims claims, long expire) {
if (claims!=null && expire>0) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime()+expire*1000);
claims.setIssuedAt(nowDate);
claims.setExpiration(expireDate);
}
return claims;
}
}
拦截器Interceptor 拦截请求,校验jwt
自定义注解,用于过滤请求方法
/**
* 自定义 注解
* 是否需要校验jwt
*
* @Target:注解的作用目标
* @Target(ElementType.TYPE)——接口、类、枚举、注解
* @Target(ElementType.FIELD)——字段、枚举的常量
* @Target(ElementType.METHOD)——方法
* @Target(ElementType.PARAMETER)——方法参数
* @Target(ElementType.CONSTRUCTOR) ——构造函数
* @Target(ElementType.LOCAL_VARIABLE)——局部变量
* @Target(ElementType.ANNOTATION_TYPE)——注解
* @Target(ElementType.PACKAGE)——包
*
*@Retention:注解的保留位置
*RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
* RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
* RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
* @Document:说明该注解将被包含在javadoc中
* @Inherited:说明子类可以继承父类中的该注解
*
*
*
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginJWT {
boolean required() default true;
}
自定义拦截Interceptor
/**
* 自定义拦截器后,需要配置进Spring
*
* 拦截器Interceptor可以拿到原始的HTTP请求和响应的信息,
* 也可以拿到你真正处理请求方法的信息,但是拿不到传进参数的那个值。
*
*拦截顺序:filter—>Interceptor-->ControllerAdvice-->@Aspect -->Controller
*
*/
@Slf4j
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Value("${learn.jwt.secret}")
private String secretKey;
JwtTokenProvider jwtTokenProvider;
/**
* 在访问Controller某个方法之前这个方法会被调用。
* @param request
* @param response
* @param handler
* @return false则表示不执行postHandle方法,true 表示执行postHandle方法
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("Token Interceptor preHandle ");
if ( !(handler instanceof HandlerMethod)){
return true;//如果不是映射到方法直接通过
}
String token = request.getHeader("token");
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(LoginJWT.class)){
LoginJWT loginJWT = method.getAnnotation(LoginJWT.class);
if (loginJWT.required()){
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
//校验token是否有效
jwtTokenProvider = new JwtTokenProvider(secretKey);
Claims claims = jwtTokenProvider.parseToken(token);
log.info("Token Interceptor preHandle claims :{}",claims);
if (claims!=null){
log.info("Token Interceptor preHandle claims 有效" );
}else {//过期了
//抛出异常
log.info("Token Interceptor preHandle claims 过期");
returnJson(response);//返回前端异常
return false;//不执行下一步chain链,直接repose返回
}
return true;
}
}
return true;//false则表示不执行postHandle方法
}
/**
* 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
* preHandle方法处理之后这个方法会被调用,如果控制器Controller出现了异常,则不会执行此方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("Token Interceptor postHandle");
}
/**
* 不管有没有异常,这个afterCompletion都会被调用
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("Token Interceptor afterCompletion");
}
}
/**
* 返回异常结果
* @param response
*/
private void returnJson(HttpServletResponse response){
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
Map<String, Object> result = new HashMap<>();
result.put("code",400);
result.put("message","用户令牌token无效");
result.put("data", null);
response.getWriter().print(result);
} catch (IOException e){
} finally {
}
}
在需要jwt验证的controller的方法上加上注解LoginJWT
@GetMapping("/test")
@LoginJWT
public String test(){
return "test";
}
网友评论