Spring Security的作用
Spring Security是Spring全家桶里面的一个项目,提供认证、授权以及应对一般攻击的保护。凭借对命令时和响应式应用程序的一流支持,它是保护基于Spring开发的应用程序的实际标准。
即这个项目主要提供3个功能:
- 认证 authentication
- 授权 authorization
- 保护 protection (一般的攻击)
介绍
Spring Security 本质上使用的是标准Servlet中的Fliter
与Servlet容器集成。这意味着它可以与任何在Servlet容器中运行的任何应用一起工作。更准确地说,你可以在没有使用Spring的基于Servlet的应用中使用Spring Sercurity。
Spring Security 的大体原理是通过一个标准过滤器,进入到自己体系的过滤器中来,通过层层过滤,将攻击者的请求过滤掉。
比如最简单的用户名密码,那么就有一个org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
实现了默认的用户名密码校验。可以在该类的attemptAuthentication
方法中设置断点来观察调用堆栈以追踪源码逻辑。
原理
本节可在初步搭建好工程之后再来看,会更加能理解。因为原理性的东西一般比较抽象。
回顾Filter
(图片以及表述均来自官网)
Spring Security的Servlet支持是建立在ServletFilter
之上,所以可以通过回顾Filter
原来对于单个HTTP请求的处理程序的流程来认识它。如下图:
客户端发送一个请求给应用程序,接着容器将创建一个包含Filter
的FilterChain
,并且Servlet
将会基于请求URI的路径处理HttpServletRequest
。在一个Spring MVC应用程序中,Servlet
是一个DispatcherServlet
的实例。至少一个Servlet
能处理单个HttpServletRequest
和HttpServletResponse
。不管怎么样,至少一个Filter
会被用于:
- 防止下游
Filter
或Servlet
被调用。在这种情况下,过滤器通常将编写HttpServletResponse
- 修改下游
Filter
或Servlet
使用的HttpServletRequest
或HttpServletResponse
Filter
的功能来自传递给它的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
}
由于Filter
会影响下游Filter
和Servlet
,因此调用每个Filter
的顺序非常重要。
DelegatingFilterProxy
Spring 提供了一个Filter
的实现,名字叫做DelegatingFilterProxy
。它允许桥街Servlet容器的生命周期和Spring的ApplicationContext
。Servlet容器允许使用自己的标准来注册Filter
,但是它不知道Spring已定义的Bean
。能通过标准的Servlet容器机制注册DelegatingFilterProxy
,并将所有工作委托给实现了Filter
接口的Spring Bean
。
以下是DelegatingFilterProxy
如何适应Filter
和FilterChain
。
DelegatingFilterProxy
从ApplicationContext
中查找 Bean Filter0 ,然后调用Bean Filter0。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);
}
DelegatingFilterProxy
的另一个好处是允许延迟查找Filter
bean实例。这很重要,因为容器需要在容器启动之前注册Filter
实例。Spring通常使用ContextLoaderListener
加载 Spring Beans,这个操作会在Filter
实例被注册完成之后才会完成。
FilterChainProxy
Spring Security的Servlet支持包含在FilterChainProxy
中。FilterChainProxy
是一个由Spring Security 提供的特别的Filter
。它通过SecurityFilterChain
来代理各种Filter
实例。因为SecurityFilterChain
是一个Bean,它是被包装在DelegatingFilterProxy
中。
SecurityFilterChain
FilterChainProxy
使用SecurityFilterChain
确定应对此请求调用哪些Spring Security过滤器。
SecurityFilterChain中的SecurityFilterChain
通常是Bean,但它们是使用FilterChainProxy
而不是DelegatingFilterProxy
注册的。 FilterChainProxy
具有直接向Servlet容器或DelegatingFilterProxy
注册的优点。
首先,它为Spring Security的所有Servlet支持提供了一个起点。因此,如果您想对Spring Security的Servlet支持进行故障排除,那么可以在FilterChainProxy中添加调试点。
其次,由于FilterChainProxy
对于Spring Security的使用至关重要,因此它可以执行不被视为可选任务的任务。例如,它清除SecurityContext以避免内存泄漏。它还使用Spring Security的HttpFirewall
来保护应用程序免受某些类型的攻击。
此外,它在确定何时调用SecurityFilterChain
时提供了更大的灵活性。在Servlet容器中,仅根据URL调用Filter
。而且,FilterChainProxy
可以利用RequestMatcher
接口,根据HttpServletRequest
中的任何内容确定调用。
实际上,FilterChainProxy
可用于确定应使用哪个SecurityFilterChain
。您的应用程序可以为不同的片提供完全独立的配置。
在多个SecurityFilterChain
情况中,由FilterChainProxy 决定应使用哪个SecurityFilterChain
。仅匹配上的第一个SecurityFilterChain
将被调用。如果请求的URL是/api/messages/
,则将首先在 SecurityFilterChain0 的/api/ **
模式下进行匹配,因此即使在 SecurityFilterChainn 上也进行了匹配,也只会调用SecurityFilterChain0。如果请求的URL是/messages/
,则在 SecurityFilterChain0 的/api/**
模式下将不匹配,因此FilterChainProxy
将继续尝试每个SecurityFilterChain
。假设没有其他匹配 SecurityFilterChainn 的SecurityFilterChain
实例将被调用。
请注意,SecurityFilterChain0 仅配置了三个安全Filter
实例。但是,SecurityFilterChainn 配置了四个Filter
。重要的是要注意,每个SecurityFilterChain
可以是唯一的,并且可以独立配置。实际上,如果应用程序希望Spring Security忽略某些请求,则SecurityFilterChain
可能没有安全Filter
。
安全Filters
安全过滤器通过SecurityFilterChain
API 被插入到FilterChainProxy
。Filter
的顺序很重要。通常不必知道Spring Security的Filters
顺序,但是,又是都了解顺序是有益的。
以下是Spring Security Filters订购的完整列表:
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
处理安全异常
ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException转换为HTTP响应。
ExceptionTranslationFilter作为安全Filter
之一插入到FilterChainProxy中。
-
首先,
ExceptionTranslationFilter
调用FilterChain.doFilter(request,response)
来调用应用程序的其余部分。 -
如果用糊没被认证或者有一个
AuthenticationException
,则启动身份验证。SecurityContextHolder
会被清除,这个里面有已认证的信息HttpServletRequest
会被缓存在RequestCache
。当用户成功认证,RequestCache
将会被用来重放原始请求。AuthenticationEntryPoint
被用来从客户端请求凭据。例如,它可能重定向到登录页面或发送WWW-Authenicate
标头。
-
否则,如果它是
AccessDeniedException
,则拒绝访问。AccessDeniedHandler
被调用以处理拒绝的访问。
如果应用程序未引发AccessDeniedExeption或AuthenticationException,则ExceptionTranlationFilter不执行任何操作。
ExceptionTranslationFilter的伪代码如下所示:
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException e) {
if (!authenticated || e instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}
FilterChain.doFilter(request, response)
等效于调用应用程序的其余部分。这意味着如果应用程序的另一部分(即FilterSecurityInterceptor
或安全方法)抛出一个AccessDeniedException
或 AuthenticationException
异常,那么它将在这里被捕获。
如果用户未被认证或者它是一个AuthenticationException
,那么将会开始认证。
否则,就拒绝访问。