• Spring-Boot 集成 JWT


    来源:https://www.fangzhipeng.com/architecture/2017/08/11/jwt.html

    介绍

    1、什么是JWT

    JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且字自包含的标准,用于将各方之间的信息地传输为JSON对象。 该信息是通过数字签名进行验证。使用HMAC算法或使用RSA的公钥/私钥对JWT进行签名,所以它的安全性非常高。

    进一步解释它的特点或者概念:

    • 紧凑型(compact):由于是加密后的字符串,JWT数据体积非常的小,可通过 POST参数或HTTP请求头发送。 另外,数据体积小意味着传输速度很快。

    • 自包含(self-contained):JWT包含了用户的所有信心,所以避免了每次查询数据库,降低了服务器的负载。

    2、JWT的应用场景

    常用于以下场景:

    • 验证:这是使用JWT的最常见的场景。 一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。 单点登录是一个广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。

    • 信息交换:JWT是在各方之间安全传输信息的好方法,因为它们可以被签名,例如使用公钥/私钥对. 另外,当使用标题和有效载荷计算签名时,还可以验证内容是否未被篡改。

    3、JWT结构:

    (1):hearder:标题通常由两部分组成:令牌的类型,即JWT,以及使用的哈希算法,如HMAC SHA256或RSA。 比如:

    {
      “alg”:“HS256”,
      “typ”:“JWT”
    }

    (2):Payload:这是JWT的第二部分,包含了用户的一些信息和Cliam(声明、权利),有三种类型的Cliam:保留,公开和私人声明。 一个典型的payload应该如下:

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }

    将payload进行Base64 编码作为JWT的第二部分。

    (3)Signature:要创建签名部分,需要使用到用Base64编码后header和payloader,以及秘钥,将它们签名,一个典型的格式如下:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

    集成

    1、引入依赖:

            <!--JWT-->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.4.0</version>
            </dependency>

    2、拦截所有的请求,进行验证

    /**
     * @author caib
     * 1.x 的版本使用 WebMvcConfigurerAdapter
     * 2.x 的版本使用 WebMvcConfigurer
     *
     * @date
     */
    @Configuration
    public class InterceptorConfig extends  WebMvcConfigurerAdapter
    {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(authenticationInterceptor())
                    .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
        }
        @Bean
        public AuthenticationInterceptor authenticationInterceptor() {
            return new AuthenticationInterceptor();
        }
    }

      

    3、自定义token 拦截器

    /**
     * @author caib
     * @date
     */
    public class AuthenticationInterceptor implements HandlerInterceptor {
    
        @Autowired
        private RedisCacheUtils redisTemplate;
    
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
            String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
            // 如果不是映射到方法直接通过
            if(!(object instanceof HandlerMethod)){
                return true;
            }
            HandlerMethod handlerMethod=(HandlerMethod)object;
            Method method=handlerMethod.getMethod();
    
            //检查是否有passtoken注释,有则跳过认证
            if (method.isAnnotationPresent(PassToken.class)) {
                PassToken passToken = method.getAnnotation(PassToken.class);
                if (passToken.required()) {
                    return true;
                }
            }
    
            //检查有没有需要用户权限的注解
            if (method.isAnnotationPresent(UserLoginToken.class)) {
                UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
                if (userLoginToken.required()) {
                    // 执行认证
                    if (token == null) {
                        throw new RuntimeException("无token,请重新登录");
                    }
                    // 获取 token 中的 user id
                    String userId ;
                    try {
                        userId = JWT.decode(token).getAudience().get(0);
                    } catch (Exception e) {
                        throw new BizException("无效的token,请重新登录");
                    }
    
              //方法1:将token存放再redis中,进行验证
                    String redisToken = redisTemplate.getCacheObject("token_" + userId);
                    // 验证 token
                    if ( !token.equals(redisToken)) {
                        throw new BizException("无效的token,请重新登录");
                    }
              
              //方法2 通过查询数据库用户信息进行验证token,其中的password 自行获取(注意数据库保存的密码是否加密)
              // 验证 token
              JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build();
              try {
                  jwtVerifier.verify(token);
              } catch (JWTVerificationException e) {
                  throw new RuntimeException("401");
              }
                    //更新token有效期 (待定)
    
                    return true;
                }
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    
        }
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    
        }
    }

      

    4、注解表示该方法是否需要进行token验证

    跳过token验证:

    /**
     * @author caib
     * @date
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PassToken {
        boolean required() default true;
    }

     需要token验证

    /**
     * @author caib
     * @date
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserLoginToken {
        boolean required() default true;
    }

      

    5、用户登录记录token信息,其中userid为用户登录名,password 为登录密码,并将token保存在redis中

     public String getToken(String userid,String password) {
            String token="";
            token= JWT.create().withAudience(userid)// 将 user id 保存到 token 里面
                    .sign(Algorithm.HMAC256(password));// 以 password 作为 token 的密钥
            return token;
      }

      

    6、自定义全局异常处理(ControllerAdvice)

      其中的Result为自定义的Controller自定义返回内,MyException 为自定义异常。

    @ControllerAdvice
    @ResponseBody
    @Slf4j
    public class GlobalExceptionHandler {
        /**
         * 所有异常报错
         * @param request
         * @param e
         * @return
         * @throws Exception
         */
        @ExceptionHandler(value=Exception.class)
        public Result allExceptionHandler(HttpServletRequest request, Exception e) throws Exception{
            log.error("服务器异常原因是:",e);
    
            return new Result("ERROR",20001,"服务器内部异常",null);
    
        }
    
        /**
         * 自定义异常处理
         * @param request
         * @param e
         * @return
         */
        @ExceptionHandler(value = MyException.class)
        public Result bizExceptionHandler(HttpServletRequest request,MyException e){
            log.error("业务异常:",e);
            return new Result("ERROR",Integer.parseInt(e.getErrorCode()),e.getErrorMsg(),null);
        }
      }

      

  • 相关阅读:
    SVG与HTML、JavaScript的三种调用方式
    求时间段的交集
    iis 搭建ftp
    C#获取MAC地址的几种方法
    c#获取本地IP和MAC地址
    【Web】Javascript、Python、Django模板配合处理URL Encode
    【Django】依赖auth.user的数据库迁移,以及admin用户非交互式创建
    【Linux】debian jessie版本安装1.9 svn
    【日志处理】logstash性能优化配置
    【Linux】apt-get install 怎么阻止弹出框,使用脚本默认自动安装?
  • 原文地址:https://www.cnblogs.com/luyilan/p/13959582.html
Copyright © 2020-2023  润新知