filter拦截登录权限

138 阅读2分钟

除了前面我们实现的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,以便可以实现依赖注入。然后我们通过手动的配置形式,为filterbean配置了过滤器的执行顺序和要过滤的请求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找到问题:

image.png

原来抛出的异常可能会被包装一层,我们需要再加一个判断:

RestBodyAdvice.java

// 响应异常的情况
if (exObj != null) {
    if (exObj instanceof NestedServletException) {
        exObj = (Exception) exObj.getCause();
    }
    // 处理全局异常的调用
    ...
}

ok,修复后再整体跑下单元测试,没毛病,老铁。通过我们前面的封装,咱发现只要业务处理有问题的地方我们只管抛异常,不用管怎么给前端响应,这就是我们在进一步开发新的模块前所做的努力。大家加油!