文章目录
概述
SpringSecurity原理(一)——初探
SpringSecurity原理(二)——认证
SpringSecurity原理(三)——授权
SpringSecurity原理(四)——过滤器
SpringSecurity原理(五)——扩展与配置
前面的文章中我们已经基本了解了怎样使用Spring Security的基本的认证(Authentication)和授权(Authority)
但是,我们还不清楚Spring Security到底是怎样执行这些流程。
这篇文章,我们就以SpringBoot为例,来梳理一下Spring Security从启动到执行这些流程的过程。
前置知识
我们知道Spring Security是通过Filter的方式来完成它的核心流程。但是:
- Spring Security到底拥有哪些Filter?
- 这些Filter是如何注入容器?
- 我们如何自定义自己的Filter?
web.xml配置
前面我们已经介绍过了,最开始如果我们要配置Filter,一般是通过web.xml的方式:
<filter>
<filter-name>deleFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>spring-bean-name</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>deleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
SpringBoot中添加Filter
在SpringBoot中可以通过@WebFilter和@ServletComponentScan注解,注入自定义的Filter。
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
}
@SpringBootApplication
@ServletComponentScan(basePackages = "vip.mycollege.filter")
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
也可以通过FilterRegistrationBean的方式,注入自定义的Filter。
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new MyFilter());
bean.addUrlPatterns("/*");
return bean;
}
}
也可以通过DelegatingFilterProxyRegistrationBean的方式。
@Configuration
public class FilterConfig {
@Bean("proxyFilter")
public Filter filter (){
return new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("proxyFilter");
bean.addUrlPatterns("/*");
return bean;
}
}
DelegatingFilterProxyRegistrationBean和FilterRegistrationBean都继承了AbstractFilterRegistrationBean,从名字上看就知道是一个RegistrationBean,也就是说会在Servlet容器启动的时候被注入。
DelegatingFilterProxyRegistrationBean会在Servlet容器中注册一个DelegatingFilterProxy,用来代理Spring IoC容器中某个指定名称的Filter bean。
FilterChainProxy
SpringBoot有一个SecurityFilterAutoConfiguration的自动配置类,就会配置一个name为springSecurityFilterChain的DelegatingFilterProxyRegistrationBean,这个类的url-pattern默认为/*,也就是说会过滤所有的请求。
name是springSecurityFilterChain是一个什么鬼呢?
答案是:FilterChainProxy。
这个类是在HttpSecurityBeanDefinitionParser的registerFilterChainProxyIfNecessary方法中注册。
HttpSecurityBeanDefinitionParser也是一个BeanDefinitionParser,因此它会通过parse方法来构建Filter类。
整个流程现在就清晰了:
- SpringBoot通过自动配置类搞了个DelegatingFilterProxyRegistrationBean
- DelegatingFilterProxyRegistrationBean会在Servlet启动的时候注册一个DelegatingFilterProxy
- DelegatingFilterProxy会默认会拦截所有的请求,然后交个一个别名为springSecurityFilterChain的FilterChainProxy
- FilterChainProxy在持有一个SecurityFilterChain的list
- SecurityFilterChain本身又持有一个Filter列表,可以通过match找出url匹配的Request交个filters处理
FilterChainProxy除了持有过滤器,默认内置了一个StrictHttpFirewall一个HTTP防火墙,它采用了严格模式,遇到任何可疑的请求,会通过抛出异常RequestRejectedException拒绝该请求。
现在我们知道了Spring Security如何收集利用Filter了。
但是,Spring Security到底背着我们弄了哪些Filter呢?
我只想说很多,要知道有哪些也很简单,在FilterChainProxy打一个断点,debug,看一下filterChains变量中的filters列表就能看到有哪些filter
默认情况下filterChains只有一个filte,就是DefaultSecurityFilterChain,看名字就知道这是一个SecurityFilterChain,他包含了一个Filter列表,默认有:
- WebAsyncManagerIntegrationFilter:与处理异步请求映射的 WebAsyncManager 进行集成
- SecurityContextPersistenceFilter: 请求前保存和请求后清除SecurityContextHolder中的安全上下文
- HeaderWriterFilter:将头信息加入响应中
- CsrfFilter:处理跨站请求伪造
- LogoutFilter:处理登出
- UsernamePasswordAuthenticationFilter:处理基于表单的登录
- DefaultLoginPageGeneratingFilter:如果没有配置登录页,生成默认登录页
- DefaultLogoutPageGeneratingFilter:如果没有登出页,生成默认登出页
- BasicAuthenticationFilter:处理HTTP BASIC认证
- RequestCacheAwareFilter:处理请求的缓存
- SecurityContextHolderAwareRequestFilter:包装请求对象request
- AnonymousAuthenticationFilter:检测SecurityContextHolder是否存在Authentication,如不存在提供一个匿名 Authentication
- SessionManagementFilter:管理 session 的过滤器
- ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常
- FilterSecurityInterceptor: 权限校验相关
重要Filter
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter本身没啥好说的,它就是一个Filter,但是因为它用得多,所以说一下。
Filter肯定先看doFilter方法,UsernamePasswordAuthenticationFilter的主要认证逻辑在attemptAuthentication:
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
很简单,就是从request中获取username和password的字段,封装成UsernamePasswordAuthenticationToken,然后扔给AuthenticationManager去执行认证,当然,最终认证逻辑肯定是像DaoAuthenticationProvider 这样的AuthenticationProvider执行。
FilterSecurityInterceptor
FilterSecurityInterceptor主要是用来做权限校验的,具体的鉴权逻辑主要在AbstractSecurityInterceptor中。
FilterSecurityInterceptor也是一个Filter,所以,还是先看doFilter方法,调用了invoke:
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
//一次请求中避免重复检查
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// 第一次调用,先设置标记,避免重复调用
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 业务逻辑调用之前,执行检查鉴权操作主要就是在这里面完成
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
// 执行具体的业务逻辑
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
// 业务逻辑调用之后,主要是处理返回结果
super.afterInvocation(token, null);
}
FilterInvocation就是FilterInvocation、ServletResponse、FilterChain的简单封装。
我们看到整个invoke的逻辑非常清晰,很像AOP的around结构。
ExceptionTranslationFilter
ExceptionTranslationFilter的逻辑有点奇怪,它主要是为了处理 AccessDeniedException 和 AuthenticationException 异常。但是并不是处理它前面产生的异常,而是它后面的Filter产生的异常,因为它前面Filter如果异常了根本到不了它这里。
它后面,默认就只有FilterSecurityInterceptor了,主要会产生AccessDeniedException授权异常,AuthenticationException是因为有一个再认证的过程。
过滤器
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- ChannelProcessingFilter
- ConcurrentSessionFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- ConcurrentSessionFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- SwitchUserFilter
- FilterSecurityInterceptor