美文网首页
springboot2.x jwt token登录校验,全局拦截

springboot2.x jwt token登录校验,全局拦截

作者: kaixingdeshui | 来源:发表于2020-10-05 10:32 被阅读0次

什么是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";
    }

相关文章

网友评论

      本文标题:springboot2.x jwt token登录校验,全局拦截

      本文链接:https://www.haomeiwen.com/subject/rbqiuktx.html