• SpringBoot利用自定义注解实现通用的JWT校验方案


    利用注解开发一个通用的JWT前置校验功能

    设计的预期:
    系统中并不是所有的应用都需要JWT前置校验,这就需要额外设计一个注解Annotation来标识这个方法需要JWT前置校验.例如:

    @GetMapping("/dosth")
    //加入自定义注解JwtToken,该Controller方法在运行前就需要进行JWT校验
    //JWT校验通过执行Controller方法
    //JWT校验未通过则直接返回校验失败
    @JwtToken
    public ResponseObject doSth(){
        return new ResponseObject("...");
    }
    

    开发步骤

    1. pom.xml引入JJWT

    <!--JJWT-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>
    

    2. application.yml增加appkey秘钥

    #JWT秘钥与Auth-Service保持一致
    app:
      secretKey: 1234567890-1234567890-1234567890
    

    3. 创建自定义注解@JwtToken

    //这个注解只能用在方法上

    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME) //运行时注解生效
    public @interface JwtToken {
        //当required=true说明请求必须包含token,未包含则报错
        //当required=false说明token不是必须的,不会中断请求的传递,交由后面的业务代码处理
        boolean required() default true;
    } 
    

    4. 在ArticleController增加doSth测试方法

      @JwtToken //dosth进行jwt校验
      @GetMapping("/dosth")
      public ResponseObject dosth() {
        return new ResponseObject("业务处理成功");
      }
    

    5. 创建TokenInterceptor实现代码拦截JWTToken业务验证逻辑

    /**
     * 执行目标URI方法前,对存放在请求头中的Jwt进行校验,校验通过执行目标方法,校验失败则提示错误
     */
    public class TokenInteceptor implements HandlerInterceptor {
        @Value("${app.secretKey}")
        private String appKey = null;
    
        /**
         * 在目标方法执行前先执行preHandle进行前置处理
         * @param request 原生请求对象
         * @param response 原生响应对象
         * @param handler 处理器对象
         * @return true-请求向后送达到Controller, false-中断请求立即产生响应
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("TokenInterceptor.preHandle()");
            // 如果不是映射到方法直接通过
            if(!(handler instanceof HandlerMethod)){
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //获取目标方法的Method对象
            Method method = handlerMethod.getMethod();
    
            response.setContentType("text/json;charset=utf-8");
    
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    
            //判断目标方法是否包含JwtToken注解
            if(method.isAnnotationPresent(JwtToken.class)){
                String token = request.getHeader("token");
                //判断请求头是否包含token属性,在拥有@JwtToken的方法处理时,未包含token则抛出安全异常
                if(token == null){
                    JwtToken jwtToken = method.getAnnotation(JwtToken.class);
                    if(jwtToken.required() == true) {
                        response.setStatus(401);//未认证
                        ResponseObject<Object> responseObject = new ResponseObject<>("SecurityException", "Token不存在,请检查请求头是否包含Token");
                        String json = objectMapper.writeValueAsString(responseObject);
                        response.getWriter().println(json);
                        return false;
                    }
    
                }else{//token存在时验证JWT的有效性
                    String base64Key = new BASE64Encoder().encode(appKey.getBytes());
                    SecretKey key = Keys.hmacShaKeyFor(base64Key.getBytes());
                    try {
                        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
                        String userJson = claimsJws.getBody().getSubject();
                        System.out.println(userJson);
                        User user = objectMapper.readValue(userJson, User.class);
                        request.setAttribute("$user",user); //将当前登录用户对象保存到请求属性
                        return true;
                    }catch (JsonProcessingException e){
                        e.printStackTrace();
                        response.setStatus(500);
                        ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
                        String json = objectMapper.writeValueAsString(responseObject);
                        response.getWriter().println(json);
                        return false;
                    }catch (JwtException e){//Jwt校验失败是抛出异常
                        e.printStackTrace();
                        response.setStatus(401);
                        ResponseObject<Object> responseObject = new ResponseObject<>(e.getClass().getSimpleName(), e.getMessage());
                        String json = objectMapper.writeValueAsString(responseObject);
                        response.getWriter().println(json);
                        return false;
                    }
    
                }
            }
            return true;
        }
    }
    

    6. 启动应用,

    http://localhost:8100/dosth

    7. 将Token中包含的用户数据与业务逻辑绑定,如果Token验证成功,TokenInterceptor会把用户信息转为User对象放入当前请求$user属性中

    之后在list方法中,使用@RequestArrtibute()进行提取即可.

    /**
     * 如果是VIP会员,可以查看所有普通文章与精选文章
     * 如果是普通会员,只能查看所有普通文章
     *
     * @return
     */
    @GetMapping("/list")
    @JwtToken(required = false)
    public ResponseObject list(@RequestAttribute(value = "$user", required = false) User user) {
        //request.getAttribute()
        System.out.println(user);
        return new ResponseObject("0", "success", articleService.list(user));
    }
    

    ArticleService增加业务处理代码,根据用户级别查看不同的结果

    /**
     * 如果是VIP会员,可以查看所有普通文章与精选文章
     * 如果是普通会员,只能查看所有普通文章
     * @return
     */
    public List<Article> list(User user){
        int level = 0;
        if(user == null || user.getGrade().equals("normal")){
            level = 1;
        }else if(user.getGrade().equals("vip")){
            level = 2;
        }
    
        List<Article> list = articleMapper.list(level);
        for(Article article:list){
            ResponseObject<Video> videoResponseObject = videoFeignClient.findByArticleId(article.getArticleId());
            article.setVideo(videoResponseObject.getData());
        }
        return list;
    }
    

    ArticleMapper进行修改,增加level参数

    @Mapper
    public interface ArticleMapper {
        @Select("select * from article where article_type <= #{value} order by create_time desc")
        public List<Article> list(int level);
    
    
    至此业务代码改造完毕
    用户未登录或普通用户只能看到ArticleType=1的普通文章
    而VIP身份用户则可以看到所有文章
    }
    
  • 相关阅读:
    转 | 禁忌搜索算法(Tabu Search)求解带时间窗的车辆路径规划问题详解(附Java代码)
    Branch and price and cut求解传统VRP问题以及VRPTW问题
    标号法(label-setting algorithm)求解带时间窗的最短路问题(ESPPRC)
    运筹学从何学起?如何快速入门精确式算法?
    转 | 模拟退火算法(SA)和迭代局部搜索(ILS)求解TSP的Java代码分享
    用Python画论文折线图、曲线图?几个代码模板轻松搞定!
    45. 截取“测试数据”后面的内容
    44. 更改oracle字符集编码american_america.zh16gbk 改为 SIMPLIFIED CHINESE_CHINA.ZHS16GBK
    18. 浏览器关闭页面时弹出“确定要离开此面吗?”
    6. concat_ws用法
  • 原文地址:https://www.cnblogs.com/Fzeng/p/14606587.html
Copyright © 2020-2023  润新知