美文网首页
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