二SpringSecurity核心组件类--2SecurityContextUserDetailsServiceAuthenticationManager
二 SpringSecurity核心组件类--2SecurityContext/UserDetailsService/AuthenticationManager
2.1 SecurityContext
SecurityContext
是安全的上下文,在用户通过spring security的校验之后,所有的验证信息数据都是保存到SecurityContext中。
public interface SecurityContext extends Serializable {
Authentication getAuthentication(); //可以获取Authentication
void setAuthentication(Authentication var1);
}
2.2 Authentication
表示当前的认证情况。
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
//密码
Object getCredentials();
//获取用户信息,userDetails
Object getDetails();
//获取用户:未认证,为username;已认证,为userDetails
Object getPrincipal();
//是否已经认证过
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
注意:
Object getPrincipal(); //获取用户:未认证,为username;已认证,为userDetails。
这四个属性及其意义如下:
getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)
getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。
isAuthenticated: 获取当前 Authentication 是否已认证。
setAuthenticated: 设置当前 Authentication 是否已认证(true or false)
2.3 SecurityContextHolder---默认是线程变量保存securityContext
SecurityContextHolder
用来获取SecurityContext中保存的数据的工具。通过使用静态方法获取SecurityContext的相对应的数据。
SecurityContext context = SecurityContextHolder.getContext();
在典型的web应用程序中,用户登录一次,然后由其会话ID标识。服务器缓存持续时间会话的主体信息。在Spring Security中,在请求之间存储SecurityContext的责任落在
SecurityContextPersistenceFilter上,默认情况下,该上下文将上下文存储为HTTP请求之间的
HttpSession属性。它会为每个请求恢复上下文
SecurityContextHolder,并且最重要的是,在请求完成时清除
SecurityContextHolder`。SecurityContextHolder是一个类,他的功能方法都是静态的(static)。
默认情况下,使用线程变量保存securityContext上下文
public class SecurityContextHolder {
// ~ Static fields/initializers
// =====================================================================================
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; //本地线程变量的mode模式
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
static {
initialize();
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// Set default
strategyName = MODE_THREADLOCAL; //默认模式时本地线程变量
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy(); //默认
}
else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}
else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}
else {
// Try to load a custom strategy
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}
//获取SecurityContext
public static SecurityContext getContext() {
return strategy.getContext();
}
final class ThreadLocalSecurityContextHolderStrategy implements
SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
2.3.1 获取当前用户信息
如下:
@RequestMapping("/info")
@ResponseBody
public String productInfo(){
String currentUser = "";
Object principl = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principl instanceof UserDetails) {
currentUser = ((UserDetails)principl).getUsername();
}else {
currentUser = principl.toString();
}
return " some product info,currentUser is: "+currentUser;
}
这里,我们通过SecurityContextHolder
来获取了用户信息,并拼接成字符串输。
2.4 userDetails
Spring Security不会出于安全目的直接使用实现。它们只是存储用户信息,这些信息稍后封装到Authentication对象中。这允许非安全相关的用户信息(如电子邮件地址,电话号码等)存储在一个方便的位置。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
2.5 UserDetailsService
这个方法,可以自行实现,从redis、db中获取用户信息,然后封装为userDetails。需要封装username、password、List
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
该接口提供给spring security作用户验证。
提到了UserDetails
就必须得提到UserDetailsService
, UserDetailsService也是一个接口,且只有一个方法loadUserByUsername
,他可以用来获取UserDetails。
通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);
方法。我们在实现loadUserByUsername
方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails
,(通常是一个org.springframework.security.core.userdetails.User
,它继承自UserDetails) 并返回。
在实现loadUserByUsername
方法的时候,如果我们通过查库没有查到相关记录,需要抛出一个异常来告诉spring security来“善后”。这个异常是org.springframework.security.core.userdetails.UsernameNotFoundException
。
2.6 AuthenticationManager
该认证管理器,处理authentication认证的request请求。
AuthenticationManager 的作用就是校验Authentication
,如果验证失败会抛出AuthenticationException
异常。AuthenticationException
是一个抽象类,因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException
,LockedException
,BadCredentialsException
等。BadCredentialsException
可能会比较常见,即密码错误的时候。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
该认证,由ProviderManager默认实现
2.6.1 ProviderManager(实现类)
主要作用,是实现AuthenticationManager的authenticate方法。其内初始化了List
ProviderManager 是的最常用的 AuthenticationManager 实现类。
ProviderManager 管理了一个 AuthenticationProvider 列表,每个 AuthenticationProvider 都是一个认证器,不同的 AuthenticationProvider 用来处理不同的 Authentication 对象的认证。一次完整的身份认证流程可能会经过多个 AuthenticationProvider。
ProviderManager 相当于代理了多个 AuthenticationProvider,他们的关系如下图:
源码如下:
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
//AuthenticationProvider集合
private List<AuthenticationProvider> providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) { //遍历providers,找到支持当前authentication的provider
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//调用AuthenticationProvider.authenticate,并返回authentication
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
//.........省略无关代码
}
2.6.2 AuthenticationProvider(多个AP被authenticationManager管理)
AuthenticationProvider 定义了 Spring Security 中的验证逻辑,我们来看下 AuthenticationProvider 的定义:
public interface AuthenticationProvider {
/** 参数:authentication -身份验证请求对象。
返回:包含凭据的完全认证对象。如果AuthenticationProvider无法支持通过的身份验证对象的身份验证,则可能返回null。在这种情况下,将尝试支持所提供的身份验证类的下一个AuthenticationProvider。*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
可以看到,AuthenticationProvider 中就两个方法:
- authenticate 方法用来做验证,就是验证用户身份。
- supports 则用来判断当前的 AuthenticationProvider 是否支持对应的 Authentication。
在一次完整的认证中,可能包含多个 AuthenticationProvider,而这多个 AuthenticationProvider 则由 ProviderManager 进行统一管理。
最常用的 AuthenticationProvider 实现类是 DaoAuthenticationProvider
2.6.3 Parent--(全局的authenticationManager,可为多个ap配置同一个parent)
每一个 ProviderManager 管理多个 AuthenticationProvider,同时每一个 ProviderManager 都可以配置一个 parent,如果当前的 ProviderManager 中认证失败了,还可以去它的 parent 中继续执行认证,所谓的 parent 实例,一般也是 ProviderManager,也就是 ProviderManager 的 parent 还是 ProviderManager。可以参考如下架构图:
从上面的分析中大家可以看出,AuthenticationManager 的初始化会分为两块,一个全局的 AuthenticationManager,也就是 parent,另一个则是局部的 AuthenticationManager。先给大家一个结论,一个系统中,我们可以配置多个 HttpSecurity,而每一个 HttpSecurity 都有一个对应的 AuthenticationManager 实例(局部 AuthenticationManager),这些局部的 AuthenticationManager 实例都有一个共同的 parent,那就是全局的 AuthenticationManager。
2.7 AuthenticationManagerBuilder(创建AM,并是securityBuilder的子类)
在关于securityBuilder的内容分析时,已经分析过其子类和父类包含的方法,所以AuthenticationManagerBuilder也具有父类功能。
查看其关键方法:
public class AuthenticationManagerBuilder extends
AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor, true);
}
//给AuthenticationManager设置parent
public AuthenticationManagerBuilder parentAuthenticationManager( AuthenticationManager authenticationManager) {
if (authenticationManager instanceof ProviderManager) {
eraseCredentials(((ProviderManager) authenticationManager)
.isEraseCredentialsAfterAuthentication());
}
this.parentAuthenticationManager = authenticationManager;
return this;
}
/**…………………………………………………………………………………………………………………………配置数据源………………………………………………………………………………………… */
//调用apply,创建securityConfigurer
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<>());
}
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
//设置userDetailsService用户搜索service
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<>(
userDetailsService));
}
//该performBuild用来创建ProviderManager(认证管理器)
//注意:由于securityBuilder使用了泛型,所以不同于httpSecurity.performBuild返回httpSecurity,这里返回的是ProviderManager,都是securityBuilder的子类
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
//创建ProviderManager
ProviderManager providerManager = new ProviderManager(authenticationProviders,
parentAuthenticationManager);
if (eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if (eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
//将providerManager注册入spring的容器中
providerManager = postProcess(providerManager);
return providerManager;
}
}
- 首先,我们可以通过调用 parentAuthenticationManager 方法来给一个 AuthenticationManager 设置 parent。
- inMemoryAuthentication、jdbcAuthentication 以及 userDetailsService 几个方法作用就是为了配置数据源,这里就不再赘述。
- 最后就是 performBuild 方法,这个方法的作用就是根据当前 AuthenticationManagerBuilder 来构建一个 AuthenticationManager 出来,AuthenticationManager 本身是一个接口,它的默认实现是 ProviderManager,所以这里构建的就是 ProviderManager。在构建 ProviderManager 时,一方面传入 authenticationProviders,就是该 ProviderManager 所管理的所有的 AuthenticationProvider,另一方面传入 ProviderManager 的 parent(其实也是一个 ProviderManager)。
不过自己配置有一个问题就是我们没有配置 ProviderManager 的 parent,没有配置的话,如果当前 ProviderManager 中认证失败的话,就直接抛出失败,而不会去 parent 中再次进行认证了(一般来说也不需要,如果系统比较复杂的话,可能需要)。
AuthenticationManagerBuilder 还有一个实现类叫做 DefaultPasswordEncoderAuthenticationManagerBuilder,作为内部类分别定义在 WebSecurityConfigurerAdapter 和 AuthenticationConfiguration 中,不过 DefaultPasswordEncoderAuthenticationManagerBuilder 的内容比较简单,重写了父类 AuthenticationManagerBuilder 的几个方法,配置了新的 PasswordEncoder,无他,所以这里我就不列出这个的源码了,感兴趣的小伙伴可以自行查看。但是这并不是说 DefaultPasswordEncoderAuthenticationManagerBuilder 就不重要了,因为在后面的使用中,基本上都是使用 DefaultPasswordEncoderAuthenticationManagerBuilder 来构建 AuthenticationManagerBuilder。
好啦,这就是 AuthenticationManagerBuilder。
那么是什么时候通过 AuthenticationManagerBuilder 来构建 AuthenticationManager 的呢?
这就涉及到我们的老熟人 WebSecurityConfigurerAdapter 了。当然,关于 WebSecurityConfigurerAdapter 本身的初始化过程,后面会介绍,今天我们主要来看下如何在 WebSecurityConfigurerAdapter 中开启 AuthenticationManager 的初始化的。
2.7.1 authenticationManager初始化流程--todo
2.8 PasswordEncoder
密码加密器。通常是自定义指定。
BCryptPasswordEncoder:哈希算法加密
NoOpPasswordEncoder:不使用加密