针对个人项目而言,平时使用的Apache Shiro太过臃肿。
因此简单记录几种轻量的授权方式,分别是使用Filter、SpringMVC内置组件实现。
一、Filter 1.1 示例 核心示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 @Configuration public class AuthFilterConfig { @Bean public FilterRegistrationBean authFilter () { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new AuthFilter()); registrationBean.setName("AuthFilter" ); registrationBean.addUrlPatterns("/test/test1/*" ); registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); return registrationBean; } @Slf 4j public static class AuthFilter implements Filter { public boolean preFilter (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String token = request.getHeader("Token" ); if (ObjectUtils.isEmpty(token)) { Resp resp = Resp.getFailureResp("授权失败" ); String s = JSONUtils.toJSONString(resp); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(s.getBytes(StandardCharsets.UTF_8)); return false ; } else { UserUtils.add(Thread.currentThread().getName()); return true ; } } public void postFilter (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { UserUtils.remove(); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (!preFilter(request, response)) { return ; } filterChain.doFilter(request, response); postFilter(request, response); } } }
1.2 优劣 请求会先经过所有的Filter,然后进入对应的Servlet进行处理,然后再回到所有的Filter进行响应处理。
1 Filter1 --> Filter2 --> ... --> FilterN --> Servlet --> FilterN --> ... --> Filter2 --> Filter1
优点
不局限于Spring框架。因为Filter本身是Servlet规范所包含的。 不影响前端页面的映射 缺点
不支持Spring的全局异常捕获。Filter链执行完成之后,才会到Servlet。而Spring的全局异常捕获是在DispatcherServlet中实现的。 只支持匹配规则,不支持排除。不过排除也是间接通过匹配实现的,需要自己实现才行。 1.3 Filter绕过问题 先简单介绍,如果我访问/test/test1/test2
是会被拦截的,那么我访问/a/b/../../test/test1/test2
呢?答案是也会被拦截。
之所以被拦截,是因为web容器进行了url标准化,url标准化可以防止一些路径遍历和恶意攻击。以tomcat为例,参考源码org.apache.catalina.connector.CoyoteAdapter
但要注意的是,如果是自己通过HttpServletRequest去getURL进行匹配,进而控制权限,这时候就会出现问题了,getURL获取到的就是未标准化的路径,需要手动标准化 。shiro<1.6.0版本存在的权限绕过问题,就是由此引发的。
针对这种问题,只要保证拦截时,权限拦截正确即可,至于放行后的请求分发,是否进行url标准化就无所谓,比如我的route-forward ,
参考
tomcat容器url解析特性研究 - 先知社区 Java安全之Filter权限绕过 - nice_0e3 - 博客园 url解析特性造成绕过访问 二、SpringMVC内置组件 2.1.1 示例 核心示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Configuration public class AuthValidatorConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new AuthValidator()) .addPathPatterns("/test/test1/**" ,"/static/**" ) .excludePathPatterns("/static/index.html" ); } public static class AuthValidator implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Token" ); if (ObjectUtils.isEmpty(token)) { Resp resp = Resp.getFailureResp("授权失败" ); String s = JSONUtils.toJSONString(resp); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(s.getBytes(StandardCharsets.UTF_8)); return false ; } else { UserUtils.add(Thread.currentThread().getName()); return true ; } } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { UserUtils.remove(); } } }
2.1.2 优劣 优点
不影响前端页面的映射 支持匹配与排除规则 支持Spring的全局异常捕获 缺点,暂无
2.2 WebMvcConfigurationSupport 2.2.1 示例 核心示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 @Configuration public class AuthValidatorConfig extends WebMvcConfigurationSupport { @Resource private Environment environment; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new AuthValidator()) .addPathPatterns("/test/test1/**" , "/static/**" ) .excludePathPatterns("/static/index.html" ); } @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { String staticPathPattern = environment.getProperty("spring.mvc.static-path-pattern" ); String staticLocation = environment.getProperty("spring.web.resources.static-locations" ); staticPathPattern = staticPathPattern == null ? "/**" : staticPathPattern; registry.addResourceHandler("/webjars/**" ).addResourceLocations( "classpath:/META-INF/resources/webjars/" ); if (ObjectUtils.isEmpty(staticLocation)) { registry.addResourceHandler(staticPathPattern).addResourceLocations( "classpath:/META-INF/resources/" , "classpath:/resources/" , "classpath:/static/" , "classpath:/public/" ); } else { registry.addResourceHandler(staticPathPattern).addResourceLocations(staticLocation); } registry.addResourceHandler("/swagger-ui/**" ).addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/" ); super .addResourceHandlers(registry); } public static class AuthValidator implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Token" ); if (ObjectUtils.isEmpty(token)) { Resp resp = Resp.getFailureResp("授权失败" ); String s = JSONUtils.toJSONString(resp); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(s.getBytes(StandardCharsets.UTF_8)); return false ; } else { UserUtils.add(Thread.currentThread().getName()); return true ; } } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { UserUtils.remove(); } } }
2.2.2 优劣 优点
权限较大。可以拦截整个路由,进行自定义映射 支持匹配与排除规则 支持Spring的全局异常捕获(本质是基于Spring的DispatcherServlet内部实现的) 缺点
页面映射需要自定义。比如swagger、部分spring配置参数等 2.3 两者对比 WebMvcConfigurationSupport
和 WebMvcConfigurer
都是 Spring MVC 中用于配置和自定义 Spring MVC 的组件,但它们有一些区别。
WebMvcConfigurationSupport
: WebMvcConfigurationSupport
是一个抽象类,它提供了一个基本的 Spring MVC 配置,并且可以被继承来自定义更复杂的配置。如果你需要对 Spring MVC 进行更深入的定制,可以继承这个类,并覆盖其中的方法来配置拦截器、视图解析器、格式化器等。通过继承 WebMvcConfigurationSupport
,你可以完全控制 Spring MVC 的配置,但同时需要自己处理更多的细节。WebMvcConfigurer
: WebMvcConfigurer
是一个接口,它提供了一组回调方法,允许你在 Spring MVC 的配置阶段进行自定义操作,而不需要继承任何类。通过实现这个接口,你可以注册拦截器、配置视图解析器、资源处理器、数据格式化器等。使用 WebMvcConfigurer
接口,可以更加简洁地实现一些配置需求,而不需要创建一个新的配置类。总的来说,WebMvcConfigurationSupport
用于更复杂的配置情况,需要继承并重写方法,可以对 Spring MVC 进行全面的定制。而 WebMvcConfigurer
是一种更简洁的方式,通过实现接口来实现一部分配置,适用于较为简单的定制需求。
一般来说,如果你需要对 Spring MVC 进行大量的自定义配置,推荐使用 WebMvcConfigurationSupport
;而对于简单的配置需求,WebMvcConfigurer
更加方便和推荐。在实际使用中,你也可以将两者结合起来,以满足不同层次的配置需求。