Spring Security的核心安全过滤器是其实现认证和授权等安全功能的关键组件。下面以UsernamePasswordAuthenticationFilterFilterSecurityInterceptor这两个重要的过滤器为例,进行源码分析。

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter主要用于处理基于表单的用户名和密码登录认证。

  1. 继承结构
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter

它继承自AbstractAuthenticationProcessingFilter,该类提供了认证处理的通用逻辑。

  1. 关键方法
    • attemptAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request,
                                            HttpServletResponse response) throws AuthenticationException {
    if (postOnly &&!request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException(
                "Authentication method not supported: " + request.getMethod());
    }

    String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
        username = "";
    }

    if (password == null) {
        password = "";
    }

    username = username.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

    return this.getAuthenticationManager().authenticate(authRequest);
}
- 首先检查请求方法是否为`POST`,如果不是且配置为仅支持`POST`方法,则抛出异常。
- 通过`obtainUsername`和`obtainPassword`方法从请求中获取用户名和密码。
- 创建一个`UsernamePasswordAuthenticationToken`,此时该`Token`处于未认证状态(`isAuthenticated`为`false`)。
- 调用`setDetails`方法设置`Authentication`对象的细节信息,如客户端IP等。
- 最后通过`AuthenticationManager`进行认证,`AuthenticationManager`会调用注册的`AuthenticationProvider`来验证用户凭据。
  • obtainUsernameobtainPassword方法
protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(usernameParameter);
}

protected String obtainPassword(HttpServletRequest request) {
    return request.getParameter(passwordParameter);
}

默认从请求参数中获取用户名和密码,参数名分别为usernamepassword,可以通过setUsernameParametersetPasswordParameter方法进行修改。

FilterSecurityInterceptor

FilterSecurityInterceptor负责对进入应用的请求进行授权检查。

  1. 继承结构
public final class FilterSecurityInterceptor extends AbstractSecurityInterceptor
        implements Filter

它继承自AbstractSecurityInterceptor,并实现了Filter接口。

  1. 关键方法
    • doFilter方法
public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    FilterInvocation fi = new FilterInvocation(request, response, chain);
    invoke(fi);
}

创建一个FilterInvocation对象,该对象包含了请求、响应和过滤器链等信息,然后调用invoke方法进行授权处理。

  • invoke方法
private void invoke(FilterInvocation fi) throws IOException, ServletException {
    // 如果已经处理过该请求,直接放行
    if (fi.getRequest() != null &&
            (fi.getRequest().getAttribute(FILTER_APPLIED) != null || observeOncePerRequest)) {
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    } else {
        // 标记该请求已处理
        if (fi.getRequest() != null) {
            fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
        }

        // 获取当前请求的配置属性
        List<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(fi);

        if (attributes == null || attributes.isEmpty()) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            // 获取当前认证的用户
            Authentication authenticated = this.authenticationManager.authenticate(
                    new PreInvocationAuthorizationToken(null, fi, attributes));

            try {
                // 进行授权检查
                this.accessDecisionManager.decide(authenticated, fi, attributes);
            } catch (AccessDeniedException accessDeniedException) {
                // 授权失败处理
                publishEvent(new AuthorizationFailureEvent(fi, attributes, authenticated, accessDeniedException));
                throw accessDeniedException;
            }
            // 授权成功,放行请求
            this.finallyInvocationChecker.beforeInvocation(fi);
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                this.finallyInvocationChecker.afterInvocation(fi, null);
            }
        }
    }
}
- 首先检查该请求是否已经被处理过,如果是则直接通过过滤器链放行请求。
- 通过`obtainSecurityMetadataSource`获取当前请求的配置属性,这些属性定义了访问该请求所需的权限等信息。
- 如果没有配置属性,则直接放行请求。
- 使用`AuthenticationManager`对一个`PreInvocationAuthorizationToken`进行认证,该`Token`包含了请求相关信息。
- 通过`accessDecisionManager`进行授权决策,如果授权失败则抛出`AccessDeniedException`并发布授权失败事件。
- 如果授权成功,则通过过滤器链继续处理请求。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐