有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot 版本 3.0.4
本系列Spring Security 版本 6.0.2
源码地址:https://gitee.com/pearl-organization/study-spring-security-demo
Spring Security
对Servlet
的支持是基于过滤器的,在请求到达Servlet
之前,通过过滤器进行认证授权校验,用户合法且有权限,放行通过,反之会跳转到登录页或拒绝访问。所以本篇文档主要介绍Spring Security
中过滤器的相关知识。
类比JAVA Web
中的过滤器,Spring Security
中的过滤器进行了各种代理和增强,可以简单理解Security
中的过滤器结构如下所示:
简要说明:
DelegatingFilterProxy
(代理过滤器)FilterChainProxy
(过滤器链代理)FilterChainProxy
根据请求,调用匹配的SecurityFilterChain
(Security
中的过滤器链)SecurityFilterChain
中的多个有序的Security
过滤器对请求进行处理,检验是否登录、是否授权… 并做出相应处理想必大家对JAVA Web
中的过滤器(Filter
)已经很熟悉了,作为三大组件之一,扮演者重要的角色。
一个简单的过滤器如下所示:
// 使用@ServletComponentScan添加在启动类上扫描该过滤器
@WebFilter(filterName = "myFilter", urlPatterns = {"/*"})
public class MyFilter implements Filter {
// 过滤器对象进行初始化调用
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// ......
}
/**
* 添加自定义过滤逻辑
*
* @param servletRequest 请求
* @param servletResponse 响应
* @param filterChain 过滤器链,由多个过滤器组成
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("对请求进行某些自定义操作");
// 激活下一个过滤器的的doFilter 方法,最后一个激活Servlet
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("对响应进行某些自定义操作");
}
// 过滤器销毁对象前被调用
@Override
public void destroy() {
// ......
}
}
客户端向应用程序发送一个请求,运行容器创建一个 FilterChain
(过滤器链),其中包含所有Filter
实例和 Servlet
。过滤器根据请求URI
路径来处理请求和响应。
在一个Spring Boot Web
应用程序中,一般只有一个Servlet
实例,也就是DispatcherServlet
,但是一般都有多个过滤器,他们按照指定的顺序,共同协作。
Spring Security
中的过滤器是通过SecurityFilterChain API
插入FilterChainProxy
中的,Filter
实例的顺序非常重要。
Spring Security
中所有的过滤器按照顺序如下所示(后续会详细介绍):
在Spring
的spring-web
模块中提供了对过滤器进行代理的类DelegatingFilterProxy
,这样就可以很方便的使用Spring
容器来管理过滤器。请求响应流程中,DelegatingFilterProxy
从容器中查找注册的过滤器 Bean
对象,然后调用 Bean
的过滤方法。
可以看到该类中包含了Spring
容器对象和被代理的过滤器:
在上一步骤中实现的过滤器,是使用Servlet
容器自己的标准来注册,所以这时并不会被Spring
容器管理,这时就可以使用DelegatingFilterProxy
进行代理,实现代码如下:
@Component("myFilter")
public class MyFilter implements Filter {//...... }
@Configuration
public class MyConfig {
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
DelegatingFilterProxyRegistrationBean filterProxy = new DelegatingFilterProxyRegistrationBean("myFilter");
filterProxy.addUrlPatterns("/*");
filterProxy.setOrder(-5);
return filterProxy;
}
}
Spring Security
提供了FilterChainProxy
代理类, 是 Spring Security
使用的核心,用于代理Spring Security
中所有的SecurityFilterChain
,在SecurityFilterChain
中又包含多个Spring Security
声明的Filter
。
FilterChainProxy
本质上是一个特殊的过滤器,通过DelegatingFilterProxy
进行代理,所以其也是一个Bean
对象。在Security
过滤器链中的过滤器,通常都是Bean
对象,通过FilterChainProxy
进行注册,与直接向Servlet
容器或 DelegatingFilterProxy
注册相比,FilterChainProxy
注册有很多优势:
Spring Security
的所有Servlet
支持提供了一个起点,如果需要对 Spring Security
的 Servlet
支持进行故障诊断,可以在在FilterChainProxy
中添加一个调试点。 SecurityContext
以避免内存泄漏、应用Spring Security
的HttpFirewall
来保护应用程序免受某些类型的攻击。SecurityFilterChain
方面提供了更大的灵活性。在Servlet
容器中,Filter
实例仅基于URL
被调用。FilterChainProxy
可以通过使用 RequestMatcher
接口,根据 HttpServletRequest
中的任何内容确定调用。FilterChainProxy
在整个流程中的作用如下图:
在Servlet
中,一组过滤器组成FilterChain
过滤器链,SecurityFilterChain
就比较好理解了,是Spring Security
提供的过滤器链,用于管理本身所有的过滤器,在上面的流程图中已有说明。SecurityFilterChain
可以被FilterChainProxy
用来确定当前请求应该调用哪些 Spring Security Filter
实例。
在整个流程中, FilterChainProxy
决定应该使用哪个 SecurityFilterChain
,只有第一个匹配的SecurityFilterChain
被调用。
比如在下图中,如果请求的URL
是 /api/messages/
,那么会匹配到左边的SecurityFilterChain
,如果都不匹配,则会调用支持/**
的 SecurityFilterChain
。
在入门篇中,我们只引入了一个spring-boot-starter-security
依赖,就可以进行登录认证,得益于Spring Boot
的自动配置。在spring-boot-autoconfigure
模块中集成了对Spring Security
的自动配置:
默认的配置是由 SecurityAutoConfiguration
和UserDetailsServiceAutoConfiguration
这两个自动配置类实现的。
SecurityAutoConfiguration
主要是导入 SpringBootWebSecurityConfiguration
配置:
@AutoConfiguration
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
// 认证事件发布者
@Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
在SpringBootWebSecurityConfiguration
配置类中,默认添加了 @EnableWebSecurity
注解启用了Spring Security
应用安全配置,并添加了一个SecurityFilterChain
,添加了Http
相关规则:
@Configuration( proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(
name = {"springSecurityFilterChain"}
)
@ConditionalOnClass({EnableWebSecurity.class})
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
@Configuration( proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
@Bean
@Order(2147483642)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// 配置所有的Http请求必须认证
((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)http.authorizeHttpRequests().anyRequest()).authenticated();
// 开启表单登录
http.formLogin();
// 开启Basic认证
http.httpBasic();
return (SecurityFilterChain)http.build();
}
}
}
UserDetailsServiceAutoConfiguration
则只是通过Yml
配置配置文件生成了一个默认的用户,以便于开发测试:
@AutoConfiguration
@ConditionalOnClass({AuthenticationManager.class})
@ConditionalOnBean({ObjectPostProcessor.class})
@ConditionalOnMissingBean(
value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, AuthenticationManagerResolver.class},
type = {"org.springframework.security.oauth2.jwt.JwtDecoder", "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"}
)
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(new UserDetails[]{User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
}
// 省略...........
}
在SecurityFilterAutoConfiguration
自动配置类中,则声明名称为springSecurityFilterChain
的过滤器将会被代理:
在上面说过SecurityFilterChain
存放着所有的过滤器,Spring Security
提供了默认实现类DefaultSecurityFilterChain
,通过HttpSecurity.build
方法构建,可以看到默认匹配所有请求,并默认存在15个过滤器:
在SecurityFilterAutoConfiguration
自动配置类中,声明名称为springSecurityFilterChain
的过滤器将会被代理,那么这个过滤器是在哪里加载的呢?
首先在SpringBootWebSecurityConfiguration
配置类中,默认添加了 @EnableWebSecurity
注解启用了Spring Security
应用安全配置, @EnableWebSecurity
会导入多个配置类:
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class})
@EnableGlobalAuthentication
public @interface EnableWebSecurity {
boolean debug() default false;
}
在WebSecurityConfiguration
中,会构建这个过滤器:
springSecurityFilterChain
会被被FilterChainProxy
代理,注册为Bean
,并存放了所有的SecurityFilterChain
:
springSecurityFilterChain
因为之前被声明过被DelegatingFilterProxy
进行关联代理,最终经过层层代理,会生成完整的DelegatingFilterProxy
类型过滤器,等待请求,并执行相关逻辑。