本文主要研究 SpringBoot-Security 的认证过程, 主要涉及的过滤器是 UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter
中做了什么UsernamePasswordAuthenticationFilter
中一些重要属性的创建过程该类存储用户的身份信息, 权限信息, 以及一些附加信息
public interface Authentication extends Principal, Serializable {
// 权限信息
Collection<? extends GrantedAuthority> getAuthorities();
// 凭据,一般获取到的是密码
Object getCredentials();
// 从请求中获取到的一些附加信息
Object getDetails();
// 一般是用户名或者 UserDetails
Object getPrincipal();
// 是否已经认证
boolean isAuthenticated();
// 设置认证结果
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
没有登录的用户直接访问接口的话, 会给该请求一个
AnonymousAuthenticationToken
的权限
通过用户名密码进行登录的时候,
SpringSecurity
对于该用户的信息存储的实现
SpringSecurity
内置的用户信息的接口
注意是
org.springframework.security.core.userdetails
包下的
public class User implements UserDetails, CredentialsContainer {
// 密码
private String password;
// 用户名
private final String username;
// 权限
private final Set<GrantedAuthority> authorities;
// 账户是否过期
private final boolean accountNonExpired;
// 是否被锁定
private final boolean accountNonLocked;
// 密码是否过期
private final boolean credentialsNonExpired;
// 是否激活状态
private final boolean enabled;
}
UserDetailsService
: 用来通过用户名获取用户信息的接口
UserDetailsManager
: 用来对用户进行增删改查的操作类
一般都会同时实现这两个接口
public interface UserDetailsService {
// 通过用户名获取用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
继承了
UserDetailsService
, 在其基础上增加了对用户进行增删改查的功能
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
基于内存的用户管理类. 同时实现了
UserDetailsService 和 UserDetailsManager
基于数据库的用户管理类, 同时实现了
UserDetailsService 和 UserDetailsManager
一般咱们自己的业务不会使用这个, 而是自己直接实现UserDetailsService
真正提供认证业务处理的类
public interface AuthenticationProvider {
// 认证, 认证成功返回一个新的认证成功的 Authentication
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// 判断 Provider 是否支持处理当前的 Authentication
boolean supports(Class<?> authentication);
}
认证匿名用户信息
AnonymousAuthenticationToken
最常用的认证服务, 一般我们认为用来认证从数据库中取出来的信息,
UsernamePasswordAuthenticationToken
用户的认证使用这个类来处理
注: 这个类里面核心业务就只是从使用UserDetailService
中获取用户信息, 有很好的扩展性
认证流程的入口
public interface AuthenticationManager {
// 认证, 主要用来判断用户是否存在, 是否可用等操作
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationProvider
的管理器,ProviderManager
中保存了一个AuthenticationProvider
的集合, 认证流程启动之后, 循环调用AuthenticationProvider#supports
并判断当前AuthenticationProvider
是否能够认证当前的用户信息UsernamePasswordAuthenticationToken
, 如果能够处理则就用当前AuthenticationProvider
处理认证流程
认证成功返回一个认证成功的Authentication
, 认证失败一般是抛出异常
后面段落会描述
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 如果该请求不需要认证, 继续下面的 filter
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
// 核心方法一: attemptAuthentication() 获取认证的结果
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
// return immediately as subclass has indicated that it hasn't completed
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功之后的操作, 保存用户信息到 SecurityContextRepository, 执行 successHandler 等
successfulAuthentication(request, response, chain, authenticationResult);
}
// 认证失败会跑出异常, 认证失败的回调
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// 仅支持 post 请求
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
// 获取用户名和密码
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
// 创建一个没有认证的 Authentication
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
// Allow subclasses to set the "details" property
// 设置 Authentication 的 detail 属性
setDetails(request, authRequest);
// 使用 AuthenticationManager 去认证 Authentication
return this.getAuthenticationManager().authenticate(authRequest);
}
为什么是
ProviderManager
后面的章节会详细说
@Override
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;
int currentPosition = 0;
int size = this.providers.size();
// 获取所有的 AuthenticationProvider
for (AuthenticationProvider provider : getProviders()) {
// 判断当前 provider 是否能够处理当前的 Authentication
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
// 使用 Provider 认证, 如果认证成功会得到一个认证成功的 Authentication
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
// 如果上面的 Provider 全部判断了之后没有能处理认证的 Provider
// 会获取到当前 Provider#parent, parent 同样也是一个 AuthenticationManager, 然后用该 parent 进行认证
// 注: 其实这个 ProviderManager 的 parent 应该还是一个 ProviderManager, 只不过里面的 provider 不一样
// 第四节会详细说这个字段的设置过程
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
// 认证成功, 移除凭证和一些安全的数据信息
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
// 这个地方的意思是说: 如果是 parent 认证成功的, 那么 parent 已经发不过认证成功的事件了, 这里就不需要发布了
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// 处理认证失败或者抛出的异常
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed then it will
// publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
// parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
// 由此可见 AuthenticationManagerBuilder 本身就是一个 SecurityBuilder, 最后会执行 performBuild()
// 从实现接口 ProviderManagerBuilder 不难看出, 这个类构建出来的对象就是一个 ProviderManager
public class AuthenticationManagerBuilder
extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
// 新建 ProviderManager 并设置 parent
ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}
if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
AuthenticationConfiguration
authenticationManagerBuilder()
向 ioc
中注入了一个 AuthenticationManagerBuilder
, 用来构建 AuthenticationManager
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
注入了 3 个Bean
都是 GlobalAuthenticationConfigurerAdapter
的实现类, 这三个类都是 AuthenticationManagerBuilder
的配置类
@Bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
ApplicationContext context) {
return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
ApplicationContext context) {
// 这个比较重要
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
ApplicationContext context) {
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
使用 @Autowired
设置了一些属性
// 重要: 获取到所有的 GlobalAuthenticationConfigurerAdapter(就是2步骤注入的3个Bean) 然后赋值给 this.globalAuthConfigurers
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(List<GlobalAuthenticationConfigurerAdapter> configurers) {
configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
有一个 getAuthenticationManager()
方法, 在 HttpSecurity
构建的时候会调用, 这个很重要
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
// 从 ioc 中获取 AuthenticationManagerBuilder
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
// 主要: 将 globalAuthConfigurers 中的配置都应用到 AuthenticationManager 上
for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
authBuilder.apply(config);
}
// 然后构建对应的对象, 其实就是 ProviderManager
this.authenticationManager = authBuilder.build();
if (this.authenticationManager == null) {
this.authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return this.authenticationManager;
}
初始化
UserDetailsService
的配置
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
// 在 init 的时候加入了另外的一个配置类
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
InitializeUserDetailsManagerConfigurer#configure
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 这里获取 ioc 中的 UserDetailsServices
// 如果我们没有自己注入的话, 会获取到 UserDetailsServiceAutoConfiguration 中注入的 InMemoryUserDetailsManager
List<BeanWithName<UserDetailsService>> userDetailsServices = getBeansWithName(UserDetailsService.class);
... // 省略一部分代码
// 创建一个 Provider
DaoAuthenticationProvider provider;
if (passwordEncoder != null) {
provider = new DaoAuthenticationProvider(passwordEncoder);
}
else {
provider = new DaoAuthenticationProvider();
}
provider.setUserDetailsService(userDetailsService);
...
// 向 AuthenticationManagerBuilder 中加了一个 DaoAuthenticationProvider
auth.authenticationProvider(provider);
...
}
// InitializeAuthenticationProviderBeanManagerConfigurer#init
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
// 添加了一个新的配置类
auth.apply(new InitializeAuthenticationProviderManagerConfigurer());
}
InitializeAuthenticationProviderManagerConfigurer#configure
@Override
public void configure(AuthenticationManagerBuilder auth) {
// auth.isConfigured() 是判断 AuthenticationManagerBuilder 中是否存在 AuthenticationProvider
if (auth.isConfigured()) {
return;
}
List<BeanWithName<AuthenticationProvider>> authenticationProviders = getBeansWithName(
AuthenticationProvider.class);
...
// 将 ioc 中的 AuthenticationProvider 添加到 AuthenticationManagerBuilder 中
auth.authenticationProvider(authenticationProvider);
...
}
// HttpSecurityConfiguration
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
// 创建一个密码加密器
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
// 新创建一个 AuthenticationManagerBuilder, 是不是很奇怪这地方为什么要在创建一个新的 AuthenticationManagerBuilder
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);
// 重要: authenticationManager() 会调用 AuthenticationConfiguration.getAuthenticationManager()
// 把 AuthenticationConfiguration 中注入的那个 AuthenticationManager 当做新创建的 DefaultPasswordEncoderAuthenticationManagerBuilder 中构建的 AuthenticationManager 的父AuthenticationManager
authenticationBuilder.parentAuthenticationManager(authenticationManager());
// HttpSecurity 的构造函数中会把新创建的 AuthenticationManagerBuilder 放入到 SharedObjects 中, 等待某一刻别的什么地方把这个 AuthenticationManagerBuilder 取出来, 然后构建成 AuthenticationManager, 接着向下看
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
...
return http;
}
@Override
protected void beforeConfigure() throws Exception {
// 如果已经有了 authenticationManager, 将 authenticationManager 放到 SharedObject 中
if (this.authenticationManager != null) {
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
// 构建 AuthenticationManager
else {
ObservationRegistry registry = getObservationRegistry();
// 从SharedObject 中获取到 AuthenticationManagerBuilder 然后构建出来 AuthenticationManager
// 然后将构建出来的 AuthenticationManager 在放到 SharedObject 中
AuthenticationManager manager = getAuthenticationRegistry().build();
if (!registry.isNoop() && manager != null) {
setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager));
}
else {
setSharedObject(AuthenticationManager.class, manager);
}
}
}
HttpSecurity
的 formLogin
方法
public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
// 向 HttpSecurity 中加了 FormLoginConfigurer,在 HttpSecurity 构建的时候执行 performBuild() 的时候会执行
FormLoginConfigurer 的
formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
return HttpSecurity.this;
}
FormLoginConfigurer
的构造器
public FormLoginConfigurer() {
// 新建了一个 UsernamePasswordAuthenticationFilter 认证过滤器, 父类 AbstractAuthenticationFilterConfigurer 会保存一下这个过滤器
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
FormLoginConfigurer
的 configure()
方法执行
基本上
UsernamePasswordAuthenticationFilter
的大部分属性都在这里设置的
// authFilter 就是 2 步骤传给父类的 UsernamePasswordAuthenticationFilter
@Override
public void configure(B http) throws Exception {
// 从 HttpSecurity 中获取到 AuthenticationManager 设置到 UsernamePasswordAuthenticationFilter
this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
// 设置认证成功处理器和认证失败处理器
this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
...
F filter = postProcess(this.authFilter);
// 将 UsernamePasswordAuthenticationFilter 添加到 httpSecurity 中
http.addFilter(filter);
}
AuthenticationManagerBuilder
, 其中 HttpSecurityConfiguration
中在创建 HttpSecurity
对象的时候创建了一个, AuthenticationConfiguration
中使用 @Bean
的方式注入了一个HttpSecurity
中创建的 AuthenticationManagerBuilder
将 @Bean
创建的 AuthenticationManagerBuilder
作为自己的 parent 保存AuthenticationConfiguration
中有个注入了一个 InitializeUserDetailsBeanManagerConfigurer
, 这个 config 在调用 AuthenticationConfiguration#getAuthenticationManager()
方法的时候会被加入到 @Bean
注入的 AuthenticationManagerBuilder
中, InitializeUserDetailsBeanManagerConfigurer
这个类会引入另外一个配置类, 然后该配置类会向 AuthenticationManagerBuilder
中添加一个 DaoAuthenticationProvider
HttpSecurity
中创建的 AuthenticationManagerBuilder
会被放到 shareObject
中, 然后在 httpSecurity#beforeConfigure()
执行的时候会构建出来一个 AuthenticationManager
放到 shareObject
中httpSecurity.formLogin()
就会在 httpSecurity
中加入 UsernamePasswordAuthenticationFilter
, 同时会向 httpSecurity
中加入一个 FormLoginConfigurer
的配置FormLoginConfigurer
在执行 configure
的时候会从 httpSecurity
的 shareObject
中获取到存放在 shareObject
中的 AuthenticationManager
, 然后给 UsernamePasswordAuthenticationFilter
对应的属性赋值ProviderManager@8987
有一个 parent 为 ProviderManager@9005
, ProviderManager@8987
里面有一个 AnonymousAuthenticationProvider
, 而 ProviderManager@9005
没有 parent, 有一个 provider 为 DaoAuthenticationProvider
httpSecurity.anonymous()
会存在第一个 ProviderManager
吗
不可能不存在的, 因为
httpSecurity
在创建的时候就已经默认已经调用了anonymous
方法, 加入了AnonymousAuthenticationProvider