基于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 设计注解DataAuthValid,增加在需要进⾏数据权限验证的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 ⾃定义DataAuthInterceptor,判断请求对应的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()){
}else{
Class<? extends AbstractDataAuth> dataAuthClass = dataAuthValid.dataAuthClass();
//                wInstance().checkDataAuth(request,response);
Bean(dataAuthClass).checkDataAuth(request,response);
}
}catch (Exception e){
<("DataAuthInterceptor执⾏请求:{}过滤异常。异常信息为:{}",RequestURI(),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异常。异常信息为:{}",RequestURI(), 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 {
springboot和过滤器
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 = Reader();
while ((adLine()) != 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 {
ad();
}
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 InputStream()));
}
}
2.6 针对上述⾃定义的Interceptor和Filter(如果系统中有swagger,新增⾃定义后可能会导
致swagger不可⽤,增加注释的两⾏代码即可
/**
* @Description 数据权限filter配置
* @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 urnUtil.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().Method())){
try{
String body = ((CustomHttpServletRequestWrapper)httpServletRequest).getBodyAsString();
UpdateSnapshotPolicy updateSnapshotPolicy = JSONObject.parseObject(body, UpdateSnapshotPolicy.class);
if(updateSnapshotPolicy == null){
return false;
}
String vmId = VmId();
List<ZkxyVirtualMachine> zkxyVirtualMachines = VirtualMachinesByUserSession();
if(!CollectionUtils.isEmpty(zkxyVirtualMachines)){
for (ZkxyVirtualMachine zkxyVirtualMachine:zkxyVirtualMachines){
VmId().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");
log.warn("请求({})已拒绝!vmId:{}",RequestURI(),vmId);
}catch (Exception e){
<("请求({})处理异常.异常信息为:{}",RequestURI(),e.getMessage());
}
}
return false;
}
}
3.
⼀开始的时候只⾃定义了Interceptor,在对POST请求中读取了HttpRequest的getReader,导致执⾏到Handler时,系统提⽰:
java.lang.IllegalStateException: getReader() has already been called for this request
org.InputStream(Request.java:1032)
org.InputStream(RequestFacade.java:364)
此时就需要⾃定义Filter()对需要的请求进⾏HttpRequest处理(),转为HttpServletRequestWrapper,然后将该HttpServletRequestWrapper 放⼊过滤链中传递下去。