• 基于SpringBoot实现请求的数据权限验证


    1.实现方案

    对需要进行数据权限的请求添加自定义注解,通过拦截器对请求进行拦截,判断是否需要进行数据权限验证和执行数据权限验证的逻辑。(GET请求没问题,POST请求因为HttpRequest的流getReader只能读取一次,如果在拦截器处理后,进入Handler会抛异常。此问题后面单独说

    2.代码实现

    2.1 抽象数据权限验证类

    /**
     * @Description 抽象的数据权限类
     * @Author zouxiaodong
     * @Date 2022/03/01 15:36
     */
    public abstract class AbstractDataAuth {
    
        /**
         * @Author zouxiaodong
         * @Description 数据权限控制逻辑
         * @Date 2022/03/01 15:42
         * @Param [httpServletRequest, httpServletResponse]
         * @return boolean
         **/
        public abstract boolean checkDataAuth(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse);
    }

    2.2 自定义注解,增加在需要进行数据权限验证的Handler上

    /**
     * @Author zouxiaodong
     * @Description 数据权限认证的注解
     * @Date 2022/03/01 15:16
     **/
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataAuthValid {
    
        /**
         * 名称
         **/
        String name() default "";
    
        /**
         * 数据权限开关,默认开启
         **/
        boolean switchAuth() default true;
    
        /**
         * 数据权限处理类
         **/
        Class<? extends AbstractDataAuth> dataAuthClass();
    }

    2.3 自定义拦截器,判断请求对应的handler是否有注解(是否需要进行权限验证)

    /**
     * @Description 数据权限拦截器
     * @Author zouxiaodong
     * @Date 2022/03/01 16:18
     */
    @Component
    @Slf4j
    public class DataAuthInterceptor extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
            try {
                // 如果不是方法
                if(!(handler instanceof HandlerMethod)){
                    return true;
                }
                DataAuthValid dataAuthValid = ((HandlerMethod) handler).getMethodAnnotation(DataAuthValid.class);
                if(dataAuthValid == null || !dataAuthValid.switchAuth()){
                    return true;
                }else{
                    Class<? extends AbstractDataAuth> dataAuthClass = dataAuthValid.dataAuthClass();
    //                return dataAuthClass.newInstance().checkDataAuth(request,response);
                    return SpringUtil.getBean(dataAuthClass).checkDataAuth(request,response);
                }
            }catch (Exception e){
                log.error("DataAuthInterceptor执行请求:{}拦截异常。异常信息为:{}",request.getRequestURI(),e.getMessage());
                return false;
            }
        }
    }

    2.4 自定义Filter(对特定请求的HttpRequest进行Wrapper处理,避免Post请求对流进行二次读取时的异常)

    /**
     * @Description 需要对post或者put请求进行数据权限验证时的filter
     * @Author zouxiaodong
     * @Date 2022/03/02 16:07
     */
    @Slf4j
    public class PostMethodFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            CustomHttpServletRequestWrapper customHttpServletRequestWrapper = null;
            HttpServletRequest req = (HttpServletRequest)request;
            try {
                customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(req);
            }catch (Exception e){
                log.warn("请求({})执行filter异常。异常信息为:{}",req.getRequestURI(), e.getMessage());
            }
            chain.doFilter((Objects.isNull(customHttpServletRequestWrapper) ? request : customHttpServletRequestWrapper), response);
        }
    }

    2.5 自定义HttpServletRequestWrapper,对HttpRequest进行处理

    /**
     * @Description 自定义请求wrapper
     * @Author zouxiaodong
     * @Date 2022/03/02 10:34
     */
    @Slf4j
    public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        private byte[] body;
    
        public byte[] getBody() {
            return body;
        }
    
        public String getBodyAsString(){
            return new String(body,StandardCharsets.UTF_8);
        }
    
        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request The request to wrap
         * @throws IllegalArgumentException if the request is null
         */
        public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            StringBuilder sb = new StringBuilder();
            String line;
            BufferedReader reader = request.getReader();
            while ((line=reader.readLine()) != null){
                sb.append(line);
            }
            this.body = sb.toString().getBytes(StandardCharsets.UTF_8);
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
    
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
    
                @Override
                public void setReadListener(ReadListener listener) {
                }
    
                @Override
                public boolean isReady() {
                    return true;
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
            };
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }
    }

    2.6 针对自定义的Interceptor和Filter进行系统配置

    /**
     * @Description 数据权限filter和interceptor配置
     * @Author zouxiaodong
     * @Date 2022/03/01 16:36
     */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Bean
        public FilterRegistrationBean servletRegistrationBean() {
            PostMethodFilter postMethodFilter = new PostMethodFilter();
            FilterRegistrationBean<PostMethodFilter> bean = new FilterRegistrationBean<>();
            bean.setFilter(postMethodFilter);
            bean.setName("postMethodFilter");
            bean.addUrlPatterns("/snapshot/updatePolicy");
            bean.setOrder(Ordered.LOWEST_PRECEDENCE);
            return bean;
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            //对所有请求进行拦截
            registry.addInterceptor(new DataAuthInterceptor()).addPathPatterns("/**").
                    //解决swagger无法访问的问题
                    excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
        }
    
        //解决swagger无法访问的问题
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
    }

    2.7 将注解加到需要进行数据权限验证的方法上

     /**
         * @Author zouxiaodong
         * @Description 更新虚拟机的快照策略(如果之前没有策略则新增,有则更新策略)
         * @Date 2021/12/08 9:51
         * @Param [vmID, snapshotPolicy]
         * @return com.zkxy.common.returnUtil.OperationResult<java.lang.Boolean>
         **/
        @ApiOperation("更新虚拟机的快照策略(如果之前没有策略则新增,有则更新策略;如果之前有策略,新的策略为空则进行策略禁用或停止)")
        @RequestMapping(value = "/updatePolicy",method = RequestMethod.POST)
        @DataAuthValid(name = "updatePolicy",switchAuth = true,dataAuthClass = UpdatePolicyDataAuth.class)
        public OperationResult<Boolean> updatePolicy(@ApiParam(name = "policy",value = "新的虚拟机快照策略",required = true) @RequestBody UpdateSnapshotPolicy policy){
            ......
        }

    2.8 实现注解中具体数据权限认证类

    /**
     * @Description 快照策略升级的数据权限认证
     * @see VMSnapshotController#updatePolicy(com.zkxy.eda.vmware.vcenter.pojo.UpdateSnapshotPolicy)
     * @Author zouxiaodong
     * @Date 2022/03/02 9:15
     */
    @Slf4j
    @Component
    public class UpdatePolicyDataAuth extends AbstractDataAuth{
    
        @Autowired
        private VirtualMachineService virtualMachineService;
    
        @Override
        public boolean checkDataAuth(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
            if(RequestMethod.POST.name().equals(httpServletRequest.getMethod())){
                try{
                    String body = ((CustomHttpServletRequestWrapper)httpServletRequest).getBodyAsString();
                    UpdateSnapshotPolicy updateSnapshotPolicy = JSONObject.parseObject(body, UpdateSnapshotPolicy.class);
                    if(updateSnapshotPolicy == null){
                        return false;
                    }
                    String vmId = updateSnapshotPolicy.getVmId();
                    List<ZkxyVirtualMachine> zkxyVirtualMachines = virtualMachineService.getVirtualMachinesByUserSession();
                    if(!CollectionUtils.isEmpty(zkxyVirtualMachines)){
                        for (ZkxyVirtualMachine zkxyVirtualMachine:zkxyVirtualMachines){
                            if(zkxyVirtualMachine.getVmId().equals(vmId)){
                                return true;
                            }
                        }
                    }
                    httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    OperationResult<ZkxyVirtualMachine> result = OperationResult.fail(null,String.format("您没有权限访问此云主机(%s)",vmId),null);
                    httpServletResponse.setHeader("Content-Type","application/json;charset=UTF-8");
                    httpServletResponse.getWriter().write(JSONObject.toJSONString(result));
                    httpServletResponse.getWriter().flush();
                    log.warn("请求({})已拒绝!vmId:{}",httpServletRequest.getRequestURI(),vmId);
                }catch (Exception e){
                    log.error("请求({})处理异常.异常信息为:{}",httpServletRequest.getRequestURI(),e.getMessage());
                }
            }
            return false;
        }
    }

     3.开发过程中的问题

    一开始的时候只自定义了Interceptor,在对POST请求拦截器中读取了HttpRequest的getReader,导致执行到Handler时,系统提示:

    java.lang.IllegalStateException: getReader() has already been called for this request
        org.apache.catalina.connector.Request.getInputStream(Request.java:1032)
        org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:364)

     此时就需要自定义Filter(步骤2.4,Filter的配置在步骤2.6)对需要的请求进行HttpRequest处理(步骤2.5),转为HttpServletRequestWrapper,然后将该HttpServletRequestWrapper放入过滤链中传递下去。

    如果系统中有swagger,新增自定义拦截器后可能会导致swagger不可用,步骤2.6中增加注释的两个配置代码即可

  • 相关阅读:
    native2ascii在线转换
    MyEclipse修改用户名和密码
    MyEclipse6.0安装SVN
    局域网远程关机
    使用 StackTrace 获得更多跟 Exception 有关的信息 http://blog.joycode.com/ghj/archive/2008/03/05/114925.aspx
    地图投影
    高斯-克吕格尔平面直角坐标系
    突破IIS的客户端连接限制(MtaEdt22.exe)
    生成SQL脚本含表数据
    服务跟踪查看器工具 (SvcTraceViewer.exe)WCF http://blogs.msdn.com/wcftoolsteamblogcn/
  • 原文地址:https://www.cnblogs.com/Java-Script/p/15959883.html
Copyright © 2020-2023  润新知