概览

Spring Security对Servlet的支持基于Servlet的Filter。

Filter

当客户端请求应用时,容器就会创建一个FilterChain, 其中包含多个FilterFilter的作用:

  • 阻止下游Filter, 或使Servlet被开始调用
  • 修改HttpServletRequest HttpServletResponse 传递给下游Filter或者Servlet

FilterChain使用示例:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

Servlet

Servlet (Server Applet),是用Java编写的服务端程序,广义上是指实现了Servlet接口的类。在Spring MVC应用中,其实现是DispatcherServlet,用于处理HttpServletRequestHttpServletResponse

DelegatingFilterProxy

Spring Web提供了Filter的实现DelegatingFilterProxy,用来桥接Servlet容器的生命周期和Spring的ApplicationContextDelegatingFilterProxy使用标准的Servlet容器注册机制进行注册,然后委托给继承Filter的Spring Bean(图中 Bean Filter)。

DelegatingFilterProxy实现的伪代码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // Lazily get Filter that was registered as a Spring Bean
    // For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // delegate work to the Spring Bean
    delegate.doFilter(request, response);
}

自动配置: SecurityFilterAutoConfiguration.securityFilterChainRegistration

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
		SecurityProperties securityProperties) {
	DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
		    DEFAULT_FILTER_NAME);
	registration.setOrder(securityProperties.getFilter().getOrder());
	registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
	return registration;
}

FilterChainProxy

FilterChainProxySpring Security提供一个特殊的Filter,通过SecurityFilterChain委托给其它Filter实例。它通常被包装在DelegatingFilterProxy

与直接注册Servlet容器或者DelegatingFilterProxy相比FilterChainProxy的优势:

  • 提供Spring Security的Sevrlet支持的起点,方便开启调试等
  • FilterChainProxy作为Spring Security的中心,提供必要任务,如:清除SecurityContext防止内存泄漏,应用HttpFirewall防止多种攻击等
  • 更灵活,提供RequestMatcher接口,来决定哪个SecurityFilterChain将被调用

WebSecurityConfiguration:

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
	    if (!hasConfigurers) {
	        WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
		    .postProcess(new WebSecurityConfigurerAdapter() {
	        });
            webSecurity.apply(adapter);
	    }
    return webSecurity.build();
}

WebSecurity

protected Filter performBuild() throws Exception {
  FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
	Filter result = filterChainProxy;
	...
	return result;
}

SecurityFilterChain

  • SecurityFilterChain包含多个Filter
  • 仅有第一个匹配的SecurityFilterChain会被调用

FilterChainProxy

private List<Filter> getFilters(HttpServletRequest request) {
    Iterator var2 = this.filterChains.iterator();

    SecurityFilterChain chain;
    do {
        if (!var2.hasNext()) {
            return null;
        }

        chain = (SecurityFilterChain)var2.next();
    } while(!chain.matches(request));

    return chain.getFilters();
}

SecurityFilterChain如何被添加到FilterChainProxy

WebSecurityConfigurerAdapter

public void init(final WebSecurity web) throws Exception {
    final HttpSecurity http = getHttp();
    web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
    ...
    });
}

WebSecurity.addSecurityFilterChainBuilder

private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

public WebSecurity addSecurityFilterChainBuilder(
    SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
    this.securityFilterChainBuilders.add(securityFilterChainBuilder);
    return this;
}
@Override
protected Filter performBuild() throws Exception {
    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
        securityFilterChains.add(securityFilterChainBuilder.build());
    }
    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		...
}

WebSecurityConfigurerAdapter

protected final HttpSecurity getHttp() throws Exception {

	AuthenticationManager authenticationManager = authenticationManager();
	authenticationBuilder.parentAuthenticationManager(authenticationManager);
	Map<Class<?>, Object> sharedObjects = createSharedObjects();

	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
		sharedObjects);
	
	configure(http);
	return http;
}

getHttp方法最后还会调用configure(http),所以我们可以重写这个方法,自定义SecurityFilterChain

@Order(99)
@Configuration
public class ApiSecurityConfigure extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/api/**")
                .authorizeRequests()
                .antMatchers("/api/health").anonymous()
                .antMatchers("/api/v1/**").hasRole("API")
                .antMatchers("/api/v2/**").hasRole("API_V2")
                .anyRequest().authenticated();
    }
}

@Order(98)
@Configuration
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/web/**")
		...
    }
}

Ant 风格路径表达式 :

?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录

Security Filters

Filter 插入SecurityFilterChain也很重要,可自行查阅

...
ExceptionTranslationFilter

FilterSecurityInterceptor

SwitchUserFilter
AliasFilter ClassNamespace Element or Attribute
CHANNEL_FILTERChannelProcessingFilterhttp/intercept-url@requires-channel
SECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttp
CONCURRENT_SESSION_FILTERConcurrentSessionFiltersession-management/concurrency-control
HEADERS_FILTERHeaderWriterFilterhttp/headers
CSRF_FILTERCsrfFilterhttp/csrf
LOGOUT_FILTERLogoutFilterhttp/logout
X509_FILTERX509AuthenticationFilterhttp/x509
PRE_AUTH_FILTERAbstractPreAuthenticatedProcessingFilter SubclassesN/A
CAS_FILTERCasAuthenticationFilterN/A
FORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-login
BASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basic
SERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilterhttp/@servlet-api-provision
JAAS_API_SUPPORT_FILTERJaasApiIntegrationFilterhttp/@jaas-api-provision
REMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-me
ANONYMOUS_FILTERAnonymousAuthenticationFilterhttp/anonymous
SESSION_MANAGEMENT_FILTERSessionManagementFiltersession-management
EXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttp
FILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttp
SWITCH_USER_FILTERSwitchUserFilterN/A

Handling Security Exceptions

ExceptionTranslationFilter作为一个Security Filters插入FilterChainProxy,可以把AccessDeniedExceptionAuthenticationException 转化成 HTTP 响应.

  1. ExceptionTranslationFilter调用FilterChain.doFilter(request, response)继续执行
  2. 如果用户未验证,或 AuthenticationException则开始验证
    • 清空SecurityContextHolder
    • HttpServletRequest缓存在RequestCache,用于成功后重现原来的请求
    • AuthenticationEntryPoint用于处理 AuthenticationException,例如:跳转到登录页,或者发送一个WWW-Authenticate
  3. 如果是一个AccessDeniedException,则访问拒绝,AccessDeniedHandler会被调用处理异常

自定义处理异常:

public class DefaultAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendRedirect("/login");
    }
}

public class DefaultAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
    }
}

配置:

@Order(99)
@Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/api/**")
                .exceptionHandling()
                .accessDeniedHandler(new DefaultAccessDeniedHandler())
                .authenticationEntryPoint(new DefaultAuthenticationEntryPoint())
                .and()
                .authorizeRequests()
                .antMatchers("/api/health").anonymous()
                .antMatchers("/api/v1/**").hasRole("API_V1")
                .antMatchers("/api/v2/**").hasRole("API_V2")
                .anyRequest().authenticated();
    }
}