interceptor拦截登录权限
前面我们对某些请求url
中的处理逻辑前加了身份验证和权限检查,我们采用最low的形式:每个方法中自己判断一遍,本节我们将使用另一种拦截机制来实现统一的登录权限拦截。
我们只希望拦截一些需要检查的controller请求url,而对于其他资源的请求都不拦截;并且我们希望在拦截处理逻辑中抛出异常的情况下,最终可以被处理目标controller执行方法的异常处理器所处理。因此,基于这样的需求,我们很显然想到用spring mvc家族中的重要一员——interceptor(拦截器)。
定义拦截器
这里我们没有实现拦截器接口HandlerInterceptor
,因为用HandlerInterceptorAdapter
的好处是,我们只需要关注要重写的方法:
package com.xiaojuan.boot.web.interceptor;
import ...
public class BasicAuthInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return super.preHandle(request, response, handler);
}
}
实现BasicAuthInterceptor
package com.xiaojuan.boot.web.interceptor;
import ...
@Slf4j
public class BasicAuthInterceptor extends HandlerInterceptorAdapter {
private final List needLoginUrlPatterns;
private final List needAdminRoleUrlPatterns;
private AntPathMatcher antPathMatcher;
@Resource
private UserService userService;
public BasicAuthInterceptor() {
needLoginUrlPatterns = new ArrayList();
needAdminRoleUrlPatterns = new ArrayList();
needLoginUrlPatterns.add("/user/profile");
needLoginUrlPatterns.add("/user/signature");
needLoginUrlPatterns.add("/admin/**");
// 是needLoginUrlPatterns的子集
needAdminRoleUrlPatterns.add("/admin/**");
antPathMatcher = new AntPathMatcher();
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
UserInfoDTO userInfo = (UserInfoDTO) WebRequestUtil.getSessionAttribute(SessionConst.LOGIN_USER);
if (matchUri(uri, needLoginUrlPatterns) && userInfo == null) {
throw new BusinessException("需要登录才能访问", BusinessError.NO_LOGIN.getValue());
}
if (matchUri(uri, needAdminRoleUrlPatterns)) {
userService.checkAdminRole(userInfo.getRole(), null);
}
return true;
}
private boolean matchUri(String uri, List patterns) {
for (String pattern : patterns) {
if (antPathMatcher.match(pattern, uri)) return true;
}
return false;
}
}
代码说明
对于要过滤的请求地址,这里我们维护了两个列表,一个代表所有要登录检查的url模式,一个代表在登录前提下要检查的管理员角色url模式,很显然,后者是前者的子集。
我们可以在当前类中直接注入需要的服务组件
UserService
,因为我们将会用@Bean
的形式来实例化它,把它注册到Spring容器中。这里的请求url的模式匹配,我们使用了
AntPathMatcher
类,比如我们可以用这种模式:/**/admin/**
来匹配/juan_mall/product/admin/category/add
这样的请求uri。拦截到目标请求后,我们要在实际业务处理前,先做一些校验工作,可以重写
HandlerInterceptor
的preHandle
方法,该方法如果校验失败,我们可以返回false
,也可以抛出异常,如果请求url匹配到目标controller方法,会被拦截的全局异常处理器处理。只要返回true
则表示放行,会由后面的controller
来处理。
配置拦截器
定义一个WebConfig
配置类,在其中通过@Bean
注解一个实例化bean的方法,从而让实例化出来的bean交给spring容器管理,这样我们就可以在BasicAuthInterceptor
中注入其他bean依赖了。
package com.xiaojuan.boot.web;
import ...
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public BasicAuthInterceptor basicAuthInterceptor() {
return new BasicAuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(basicAuthInterceptor()).addPathPatterns("/user/**", "/admin/**");
}
}
然后,我们实现WebMvcConfigurer
的addInterceptors
来注册拦截器,并指定拦截器要拦截的请求路径模式。
调整并测试controller
现在我们将UserController
和UserAdminController
中相关方法进行逻辑简化,只需要关注session
中内容的存取,改造的代码这里省略。最后我们测试下WebControllerTest
,测试ok!
存在的问题
现在我们来思考下,我们之前的实现中,有哪些可以改进或者存在问题的地方。
很显然,拦截器要拦截的url模式、需要登录才能访问的请求url模式和需要管理员角色才能访问的请求url模式,这些最好配置起来,也就是说,我们可以把它们配置到application.yml
中,这是一个可以改进的点。
全局异常处理可能失效
使用拦截器,有一个需要注意的坑,当我们的请求地址无法映射到目标的controller服务方法,也就是,是一个无效的请求地址,发过来被拦截器拦截抛出异常后,无法被异常处理器接管时,会默认返回500的错误格式,如下:
说明我们的全局异常处理还是存在漏洞的,下一节我们将修复试着修复这个漏洞,实现一个更强大的全局异常处理方案
controller日志切面失效
当请求被拦截后如果不放行,也就是说,没机会执行controller,那么自然针对controller方法执行的切面就不会生效,因为压根儿就没执行目标的controller方法,自然我们先前的WebLogAspect
就不能工作了:
所以说,针对controller的aop日志输出也是存在漏洞的,因为一些通过filter或interceptor的前置校验不通过,不会往后面的controller走,这也是我们要修复的问题。
好了,本节我们在引入了interceptor
来实现拦截功能后,貌似变得不轻松了,出现了一些程序的漏洞。我们的学习实践不可避免的会遇到漏洞并试着一点点把这些漏洞给补上,大家加油!