Authentication 是一个接口,用来表示用户认证信息的,在用户登陆认证以前相关信息会封装为一个 Authentication 具体实现类的对象,在登陆认证成功以后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,而后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。spring
认证流程:用户登录时AuthenticationProcessingFilter会拦截请求,调用AuthenticationManager(AuthenticationManager 是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法 authenticate()),AuthenticationManager的默认实现是ProviderManager,而且ProviderManager不处理,它交给它配置的AuthenticationProvider 列表来处理,而后依次的调用每一个AuthenticationProvider 来认证,只要有一个AuthenticationProvider认证的结果不为空,则认证成功,将其结果做为ProviderManager 的认证结果。若是全部的AuthenticationProvider的结果都为空,那么认证失败,抛出ProviderNotFoundException。数据库
若是用户直接访问登陆页面,那么认证过程跟上节描述的基本一致,只是在认证完成后将跳转到指定的成功页面,默认是应用的根路径。若是用户直接访问一个受保护的资源,那么认证过程将以下:缓存
在上述步骤中将有不少不一样的类参与,但其中主要的参与者是 ExceptionTranslationFilter。安全
校验认证请求最经常使用的方法是根据请求的用户名加载对应的 UserDetails,而后比对 UserDetails 的密码与认证请求的密码是否一致,一致则表示认证经过。Spring Security 内部的 DaoAuthenticationProvider 就是使用的这种方式。session
UserDetailsService 来负责加载 UserDetails,在认证成功之后会使用加载的 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中。框架
使用NameSpace时, authentication-manager 元素的使用会使 Spring Security 在内部建立一个 ProviderManager,而后能够经过 authentication-provider 元素往其中添加 AuthenticationProvider。当定义 authentication-provider 元素时,若是没有经过 ref 属性指定关联哪一个 AuthenticationProvider,Spring Security 默认就会使用 DaoAuthenticationProvider。使用了 NameSpace 后咱们就不要再声明 ProviderManager 了。ide
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref="userDetailsService"/> </security:authentication-manager>
若是没有使用NameSpace,咱们要在ApplicationContext中声明一个ProviderManager。spa
默认状况下,在认证成功后 ProviderManager 将清除返回的 Authentication 中的凭证信息,如密码。因此若是你在无状态的应用中将返回的 Authentication 信息缓存起来了,那么之后你再利用缓存的信息去认证将会失败,由于它已经不存在密码这样的凭证信息了。因此在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置 ProviderManager 的 eraseCredentialsAfterAuthentication 属性为 false,或者想办法在缓存时将凭证信息一块儿缓存。线程
经过 Authentication.getPrincipal() 的返回类型是 Object,但不少状况下其返回的实际上是一个 UserDetails 的实例。UserDetails是Spring Security里面的核心接口,封装了得到认证信息的方法。登陆认证的时候 Spring Security 会经过 UserDetailsService 的 loadUserByUsername() 方法获取对应的 UserDetails 进行认证,认证经过后会将该 UserDetails 赋给认证经过的 Authentication 的 principal,而后再把该 Authentication 存入到 SecurityContext 中。以后若是须要使用用户信息的时候就是经过 SecurityContextHolder 获取存放在 SecurityContext 中的 Authentication 的 principal。code
<!-- 用于认证的 AuthenticationManager --> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref="userDetailsService" /> </security:authentication-manager> <bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref="dataSource" /> </bean>
上述代码中咱们使用的 JdbcDaoImpl 是 Spring Security 为咱们提供的 UserDetailsService 的实现,另外 Spring Security 还为咱们提供了 UserDetailsService 另一个实现,InMemoryDaoImpl。
其做用是从数据库中加载 UserDetails 信息。其中已经定义好了加载相关信息的默认脚本,这些脚本也能够经过 JdbcDaoImpl 的相关属性进行指定。关于 JdbcDaoImpl 使用方式会在讲解 AuthenticationProvider 的时候作一个相对详细一点的介绍。
模块划分:
可能你早就有这么一个疑问了,既然 SecurityContext 是存放在 ThreadLocal 中的,并且在每次权限鉴定的时候都是从 ThreadLocal 中获取 SecurityContext 中对应的 Authentication 所拥有的权限,而且不一样的 request 是不一样的线程,为何每次均可以从 ThreadLocal 中获取到当前用户对应的 SecurityContext 呢?在 Web 应用中这是经过 SecurityContextPersistentFilter 实现的,默认状况下其会在每次请求开始的时候从 session 中获取 SecurityContext,而后把它设置给 SecurityContextHolder,在请求结束后又会将 SecurityContextHolder 所持有的 SecurityContext 保存在 session 中,而且清除 SecurityContextHolder 所持有的 SecurityContext。这样当咱们第一次访问系统的时候,SecurityContextHolder 所持有的 SecurityContext 确定是空的,待咱们登陆成功后,SecurityContextHolder 所持有的 SecurityContext 就不是空的了,且包含有认证成功的 Authentication 对象,待请求结束后咱们就会将 SecurityContext 存在 session 中,等到下次请求的时候就能够从 session 中获取到该 SecurityContext 并把它赋予给 SecurityContextHolder 了,因为 SecurityContextHolder 已经持有认证过的 Authentication 对象了,因此下次访问的时候也就再也不须要进行登陆认证了。