一、简介
Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架,需要Java 8或更高版本的运行环境。
它通过使用标准的Servlet Filter来集成Servlet容器,这意味着它可以与在Servlet容器中运行的任何应用程序一起工作。
更具体地说,您无需在基于Servlet的应用程序中使用Spring即可利用Spring Security。
二、Spring Security 过滤链
Spring Security对Servlet的支持是基于Servlet的Filter,因此首先了解Filter是非常有帮助的。
下图显示了单个 HTTP 请求的处理程序的典型分层:
客户端向应用程序发送请求,然后容器创建了一个FilterChain,这个FilterChain含许多的Filter和一个Servlet,
这些Filter和Servlet能够对基于请求URI路径的HttpServletRequest进行处理。
在Spring MVC应用中,Servlet是一个DispatcherServlet实例。
最多只能有一个Servlet处理HttpServletRequest和HttpServletResponse,
但是Filter都可以用来阻止下游的Filter或Servlet被调用,或修改下游Filter的HttpServletRequest或HttpServletResponse。
例如:
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 }
Spring提供了一个Filter接口的实现类(DelegatingFilterProxy),它连接了Servlet容器的生命周期和Spring的ApplicationContext。
Servlet容器允许用它自己的标准注册过滤链,但是它不知道Spring定义的Bean。
DelegatingFilterProxy可以通过标准Servlet容器机制进行注册,但是它将所有工作委托给实现了Filter接口的Bean实例。
下图是对上文的介绍:
DelegatingFilterProxy从ApplicationContext中查找Filter的第一个Bean,然后调用该Bean。
另外,DelegatingFilterProxy允许延迟加载Filter Bean实例,因为容器需要在启动前注册Filter实例。
然而,Spring通常等到Filter注册完后才用ContextLoaderListener去加载Filter实例。
Spring Security对Servlet的支持主要靠FilterChainProxy,它是Spring Security提供的一个特别的Filter。
FilterChainProxy允许委派许多Filter实例通过SecurityFilterChain(它是一个Bean,通常被包裹在DelegatingFilterProxy中)。
如图所示:
FilterChainProxy使用SecurityFilterChain决定为请求调用哪个SpringSecurity Filter。
在SecurityFilterChain中的Security Filter通常都是Bean,它们通常都是被FilterChainProxy注册,而不是DelegatingFilterProxy。
FilterChainProxy为直接注册在DelegatingFilterProxy或Servlet提供了很多优势:
1、FilterChainProxy为Spring Security 的所有Servlet支持提供了一个入口。
2、FilterChainProxy为决定什么时候应该调用SecurityFilterChain提供了灵活性。
因此,如果您尝试对 Spring Security的Servlet支持进行故障排除,则为FilterChainProxy打断点调试是一个很好的开始。
在Servlet容器中,Filter的调用仅仅基于URL,但FilterChainProxy通过调用RequestMatcher接口可以决定任何调用。
事实上,FilterChainProxy可以用于决定哪个SecurityFilterChain应该被调用:
若有多个SecurityFilterChain,FilterChainProxy将决定那个SecurityFilterChain应该被使用。
只有第一个匹配的SecurityFilterChain将会被调用。
如果一个请求URL是"/api/messages"的话,那么只有SecurityFilterChain0将会被调用,甚至该URL也匹配其他的。
三、处理安全异常
ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException反应给HTTP响应。
ExceptionTranslationFilter作为安全Filter之一被插入到FilterChainProxy。
首先ExceptionTranslationFilter调用FilterChain.doFilter(request,response)去调用剩余的应用程序。
如果用户没有认证或者发生认证异常,然后就开始认证。
SecurityContextHolder被清空,HttpServletRequest保存在RequestCache中,
当认证成功后,RequestCache将重放原始请求。
AuthenticationEntryPoint用来向客户端请求凭据,他可能是重定向到登陆页面或者发送一个WWW-Authenticate头部。
否则,如果是AccessDeniedException,那么AccessDenieHandler将会被调用。
用伪代码表示他将会是这样:
try { filterChain.doFilter(request, response); } catch (AccessDeniedException | AuthenticationException ex) { if (!authenticated || ex instanceof AuthenticationException) { startAuthentication(); } else { accessDenied(); } }
四、认证
1、SecurityContextHolder:Spring Security存储认证信息的地方。
2、SecurityContext:从SecurityContextHolder中获取,它含有当前已通过身份认证的用户。
3、Authentication:SecurityContext中的当前用户,或者是用户提供给AuthenticationManager验证的凭证。
4、GrantedAuthority:拥有的权限。
5、AuthenticationManager:定义Spring Security的Filter如何执行身份验证的API。
6、ProviderManager:AuthenticationManager最常见的实现类。
7、AuthenticationProvider:ProviderManager用来执行特定的身份验证。
8、AbstractAuthenticationProcessingFilter:身份验证的基础。
Ⅰ、SecurityContextHolder是身份验证的核心:
用户经过身份验证最简单的方法是直接设置SecurityContextHolder(存储认证信息的地方)。
SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); context.setAuthentication(authentication); SecurityContextHolder.setContext(context);
1、创建一个新的SecurityContext实例而不是用SecurityContextHolder.getContext().setAuthentication(authentication)
是很重要的,它可以避免多线程下的竟态条件。
2、Spring Security不关心在SecurityContext上关于Authentication什么类型的实现,这里使用的是TestingAuthenticationToken,
是因为它很简单,通常使用的都是UsernamePasswordAuthenticationToken(userDetails,password,authorities)
3、最后,在SecurityContextHolder上设置SecurityContext,Spring Security将会使用认证信息。
如果你想要获取验证信息,你可以像下面这样做:
SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); String username = authentication.getName(); Object principal = authentication.getPrincipal(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下,SecurityContextHolder使用ThreadLocal去存储这些信息。
这意味着对于相同线程下的方法,甚至不将它作为参数传递给其他方法,SecurityContext也是可用的。
如果处理当前主体的请求后想清除线程,这种情况下也是非常安全的。
因为Spring Security的FilterChainProxy确保SecurityContext总是被清除的。
Ⅱ、Authentication在Spring Security中有两个主要用途:
1、作为AuthenticationManager中的输入提供身份验证的凭据。
2、代表当前认证通过的用户,可以从SecurityContext中获取。
Authentication包含如下:
principal:标识用户。使用用户名/密码进行身份验证时,这通常是UserDetails实例。
credentials:通常是密码。在许多情况下,这将在用户经过身份验证后被清除,以确保它不被泄露。
authorities:授予用户的高级别权限。
Ⅲ、ProviderManager委托了一个AuthenticationProvider列表,每个AuthenticationProvider都有机会认证。
AuthenticationProvider可以提供一个特定的身份认证,例如:
DaoAuthenticationProvider通过用户名密码身份验证,JwtAuthenticationProvider通过JWT token身份认证。
ProviderManager还允许配置一个可选的父级AuthenticationManager,
如果没有一个AuthenticationProvider能够进行认证,就向它进行咨询,它通常是ProviderManager的实例。
实际上,多个ProviderManager实例可能共享相同的父级AuthenticationManager。
这是很常见的情况:多个SecurityFilterChain实例有相同的身份认证(相同的父级AuthenticaManager),
但是不同的验证机制(不用的ProviderManaer实例)。
默认情况下,ProviderManager将会试图清除认证成功的Authentication中的敏感信息,
这样可以防止密码之类的信息被保留的时间超过HttpSession所需的时间。
Ⅳ、AuthenticationEntryPoint用于发送从客户端请求凭据的 HTTP 响应。
有时客户端会通过用户名密码去请求资源,在这种情况下,Spring Security不需要提供从客户端请求凭据的HTTP 响应。
其他情况下,客户端向它们无权访问的资源发出未经身份验证的请求,它的实现类通常用于请求用户凭据。
例如:重定向到登录页,用www-Authenticate头部进行响应。
Ⅴ、AbstractAuthenticationProcessingFilter是最基础的用户身份凭据认证Filter。
在用户凭据被认证之前,Spring Security通常用AuthenticationEntryPoint请求用户凭据。
接下来,AbstractAuthenticationProcessingFilter能够验证任何提交给它的身份验证请求。
1、当用户提交凭据时,AbstractAuthenticationProcessingFilter从HttpServletRequest创建Authentication去验证,
被创建的Authentication依赖于AbstractAuthenticationProcessingFilter的子类。例如:UsernamePasswordAuthenticationFilter
通过提交给HttpServletRequest的用户名和密码来创建一个UsernamePasswordAuthenticationToken。
2、接下来这个Authentication被传递给AuthenticationManager去验证,如果验证失败:
那么SecurityContextHolder会被清空,RememberMeServices.loginFail会被调用,AuthenticationFailureHandler会被调用。
如果验证成功:
SessionAuthenticationStrategy将会收到新的登录通知,这个Authentication将会被设置到SecurityContextHolder中,
然后SecurityContextPersistenceFilter保存SecurityContext到HttpSession中。
RememberMeServices.loginSuccess被调用,如果没有配置记住我,它将会是no-op。
ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent,最后AuthenticationSuccessHandler被调用。
Ⅵ、Username/Password Authentication
Spring Security提供了以下内置机制,用于从HttpServletRequest中读取用户名和密码。
Form Login、Basic Authentication、Digest Authentication。
以Form Login为例:
用户向未经授权的资源发出未经身份验证的请求"/private",
Spring Security的FilterSecurityInterceptor抛出AccessDeniedException异常提示该请求没有经过认证。
由于用户没有通过身份验证,ExceptionTranslationFilter通过AuthenticationEntryPoint发起身份认证并发送一个重定向到登录页。
大多数情况下,AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的实例。
然后,浏览器将请求将其重定向到的登录页面,当用户名和密码被提交后:
UsernamePasswordAuthenticationFilter就开始验证用户名和密码,
UsernamePasswordAuthenticationFilter继承于AbstractAuthenticationProcessingFilter,所以下图看起来会很相似:
当用户提交用户名密码后,UsernamePasswordAuthenticationFilter创建一个UsernamePasswordAuthenticationToken,
它是从一个从HttpServletRequest抽取用户名和密码创建的一个Authentication类型。
接下来UsernamePasswordAuthenticationToken将会被传递给AuthenticationManager去认证。
五、SpringSecurity Web 与 SpringSecurity OAuth2的区别与联系
想要知道他们之间的区别和联系,我们可以先了解下什么是OAuth2:OAuth2
OAuth2通常都是用于第三方登录的,那他们之间的区别通俗点说就是主和宾的关系。