除了前面我们实现的interceptor,我们也可以开发filter组件来实现请求的过滤,对需要身份验证和授权的请求进行拦截和检查。接下来我们将替换之前的实现。开干!
开发filter组件
首先,我们开发的filter组件会从OncePerRequestFilter 继承,该组件实现了每个请求只处理一次,如果请求被转发,不会再被过滤一次。这里我们开发一个抽象类BasicRequestFilter,因为我们要捕获子类中抛出的异常,然后把它存到request作用域的指定的key中,这样我们之前改造的全局异常处理方案就有能里来获取并响应了,这里我们采用的是抽象模板方法的设计,看下代码:
package com.xiaojuan.boot.common.web.filter;
import ...
public abstract class BasicRequestFilter extends OncePerRequestFilter {
public abstract void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
doFilter(request, response, filterChain);
} catch (Exception ex) {
request.setAttribute(RequestConst.EXCEPTION, ex);
throw ex;
}
}
}
实际,我们的子类只要实现抽象方法doFilter即可。下面我们把之前interceptor的校验逻辑都搬过来,并且把原来的interceptor组件从项目移除掉,看下BasicAuthFilter的代码实现:
package com.xiaojuan.boot.web.filter;
import ...
@Slf4j
public class BasicAuthFilter extends BasicRequestFilter {
private final List<String> needLoginUrlPatterns;
private final List<String> needAdminRoleUrlPatterns;
private AntPathMatcher antPathMatcher;
@Resource
private UserService userService;
public BasicAuthFilter() {
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 void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
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);
}
filterChain.doFilter(request, response);
}
private boolean matchUri(String uri, List<String> patterns) {
for (String pattern : patterns) {
if (antPathMatcher.match(pattern, uri)) return true;
}
return false;
}
}
配置filter
我们通过@Bean注解将开发的filter组件实例化为一个spring bean,以便可以实现依赖注入。然后我们通过手动的配置形式,为filter的bean配置了过滤器的执行顺序和要过滤的请求url模式,配置如下:
package com.xiaojuan.boot.web;
import ...
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public BasicAuthFilter basicAuthFilter() {
return new BasicAuthFilter();
}
@Bean
public FilterRegistrationBean<BasicAuthFilter> basicAuthFilterBean(){
FilterRegistrationBean<BasicAuthFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(basicAuthFilter());
bean.setName("basicAuthFilter");
bean.addUrlPatterns("/user/*", "/admin/*");
bean.setOrder(1);
return bean;
}
}
测试
在测试时发现一个bug,通过debug找到问题:
原来抛出的异常可能会被包装一层,我们需要再加一个判断:
RestBodyAdvice.java
// 响应异常的情况
if (exObj != null) {
if (exObj instanceof NestedServletException) {
exObj = (Exception) exObj.getCause();
}
// 处理全局异常的调用
...
}
ok,修复后再整体跑下单元测试,没毛病,老铁。通过我们前面的封装,咱发现只要业务处理有问题的地方我们只管抛异常,不用管怎么给前端响应,这就是我们在进一步开发新的模块前所做的努力。大家加油!