上面我們一起開始了Spring Security的初體驗,並通過簡單的配置甚至零配置就可以完成一個簡單的認證流程。可能我們都有很大的疑惑,這中間到底發生了什么,為什么簡單的配置就可以完成一個認證流程啊,可我啥都沒看見,沒有寫頁面,沒有寫接口。這一篇我們將深入到源碼層面一起來了解一下spring security到底是怎么工作的。
准備工作
在開始源碼理解前,我們先來做一項基本的准備工作,從日志中去發現線索,因為我們發現什么都沒有配置的情況下,他也可以正常的工作,並給我們預置了一個臨時的用戶user。那么他肯定是在工程啟動的時候做了什么事情,上一篇我們也提到了是如果生成user用戶和密碼的。這篇我們將仔細的去了解一下。
1、首先我們配置在applicaiton.yml
中調整一下日志級別
logging:
level:
org.springframework.security: debug
我們將security
相關的日志打印出來,一起來啟動或者運行的時候到底發生了什么。
2、啟動spring-security-basic
工程
!!!找到了
日志過濾
(1) Eagerly initializing {org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration=org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration@52e04737}
(2) Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).
(3) Adding web access control expression 'authenticated', for any request
(4) Validated configuration attributes
逐個解析
1、WebSecurityEnablerConfiguration
告訴我們它初始化了一個配置類WebSecurityEnablerConfiguration
不管!找到源碼再說
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean(
name = {"springSecurityFilterChain"}
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
public WebSecurityEnablerConfiguration() {
}
}
???怎么只有這么一點東西,這個類為什么會在初始化的時候啟動?這里簡單的指出來
首先找到spring-boot-autoconfigure-版本.jar
下的META-INF/spring.factorites
文件,其中有這樣一段
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
我們可以暫時不去深究這是什么意思,總之,在springboot
啟動的時候,會將這里配置走一遍(后期可能也會寫一點關於springboot
啟動原理的文章...)我們一個一個來看一下
1.1 SecurityAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
public SecurityAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
在這個類中我們重點關注
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})
首先是SecurityProperties
@ConfigurationProperties(
// A 前綴
prefix = "spring.security"
)
public class SecurityProperties {
// ..
private SecurityProperties.User user = new SecurityProperties.User();
// ...
public static class User {
// 默認指定一個
private String name = "user";
// 默認隨機密碼
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList();
// 默認密碼是系統生成的(重點關注一下)
private boolean passwordGenerated = true;
// ...
public void setPassword(String password) {
// 如果指定了自定義了密碼,那就false 並覆蓋password
if (StringUtils.hasLength(password)) {
this.passwordGenerated = false;
this.password = password;
}
}
//.....
}
// .....
}
篇幅問題這里我刪除了很多代碼。直接看里面的注釋就好了,這也就是為什么我們不配置任何信息,也有一個默認的用戶,以及我們用配置信息覆蓋了默認用戶的關鍵信息所在。
其次是@Import
注解,這個其實就是xml配置方式中的標簽
SpringBootWebSecurityConfiguration
WebSecurityEnablerConfiguration
SecurityDataConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class SpringBootWebSecurityConfiguration {
public SpringBootWebSecurityConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
// 其實也沒干啥,就是一個空的對象,什么也沒覆蓋
@Order(2147483642)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
DefaultConfigurerAdapter() {
}
}
}
他們指向了一個關鍵的配置@ConditionalOnBean({WebSecurityConfigurerAdapter.class})
需要WebSecurityConfigurerAdapter
才會進行加載,那這個關鍵的類是什么時候加載的呢?這就回到了我們在日志中發現的第一個加載的類信息WebSecurityEnablerConfiguration
上面有個一非常關鍵的注解@EnableWebSecurity
瞧瞧干了啥
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
// 引入了配置類 WebSecurityConfiguration
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
1.2 WebSecurityConfiguration
原來,首先他是個配置注解,也import
了WebSecurityConfiguration
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 1、聲明一個 webSecurity 一起來看一下他是什么時候初始化的
private WebSecurity webSecurity;
// 2、是否為調試模式
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
// 3、關鍵點,后置對象處理器,用來初始化對象
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
// 6 、如果每沒初始化,直接指定獲取對象 WebSecurityConfigurerAdapter
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
// 7、 開始構建對象 webSecurity
return webSecurity.build();
}
// 4、通過setter方式注入 webSecurityConfigurers
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
// 獲取 0 步中獲取到的對象信息
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
// 5、 這里通過后置對象處理器來進行 webSecurity 的初始化
webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
// 放入到 AbstractConfiguredSecurityBuilder 的配置集合中
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
// 0 先自動織入webSecurityConfigurers
// 關鍵點就是獲取 beanFactory.getBeansOfType(WebSecurityConfigurer.class);
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
}
上面我們已經看到了步驟7,通常情況下都會去build
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
// 這里調用doBuild的最終方法
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
public final O getObject() {
if (!this.building.get()) {
throw new IllegalStateException("This object has not been built");
}
return this.object;
}
// 這里是抽象方法,直接找到其唯一的子類 AbstractConfiguredSecurityBuilder
protected abstract O doBuild() throws Exception;
}
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
// 前置檢查
beforeInit();
// 初始化
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
不知不覺我們已經找到了spring
中的關鍵方法init
了,很多時候我們在定義接口是都會有一個init
方法來定義注入時調用
前面我們知道 SpringBootWebSecurityConfiguration
初始化了一個對象,同時也通過AutowiredWebSecurityConfigurersIgnoreParents
拿到了WebSecurityConfigurerAdapter
的子類 DefaultConfigurerAdapter
,現在開始init()
,其實就是開始WebSecurityConfigurerAdapter
的init()
方法。說了這里可能有的同學就會比較熟悉了,這就是關鍵配置的適配器類。
代碼稍后貼出來,暫時先不看,到這里為止,我們才梳理了springboot
自動配置中的SecurityAutoConfiguration
下面我們才開始第二個類
2、 UserDetailsServiceAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({AuthenticationManager.class})
@ConditionalOnBean({ObjectPostProcessor.class})
@ConditionalOnMissingBean(
value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class},
type = {"org.springframework.security.oauth2.jwt.JwtDecoder", "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"}
)
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);
public UserDetailsServiceAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
type = {"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository"}
)
// 這里加載了從配置文件或者默認生成的用戶信息,以及加密方法
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(new UserDetails[]{org.springframework.security.core.userdetails.User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
}
private String getOrDeducePassword(User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
}
}
這里也出現了一個info
日志,當我們使用默認user
用戶時,密碼會從這里打印在控制台
這個配置類的關鍵就是生成一個默認的InMemoryUserDetailsManager
對象。
4、SecurityFilterAutoConfiguration
這個類就不詳細介紹了,就是注冊一些過濾器。
回到WebSecurityConfigurerAdapter
這個適配器類,我們關注基本的init()
方法,其他的都是一些默認的配置
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
這里有一個關鍵的方法getHttp()
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
// 獲取創建共享的對象
Map<Class<?>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
// httpHttpSecurity 的表單配置
configure(http);
return http;
}
我們簡單列舉幾個重要的方法
// 根據系統加載的AuthenticationManagerBuilder 在裝配用戶
protected UserDetailsService userDetailsService() {
AuthenticationManagerBuilder globalAuthBuilder = context
.getBean(AuthenticationManagerBuilder.class);
return new UserDetailsServiceDelegator(Arrays.asList(
localConfigureAuthenticationBldr, globalAuthBuilder));
}
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
// 資源保護
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
// 認證頁面
.formLogin().and()
// HTTP Basic authentication.
.httpBasic();
}
上面我們都是通過啟動日志的信息來理解應用在啟動時到底做了什么,加載了什么關鍵信息,接下來我們將通過運行時的日志看來看一下我們在認證過程中是如何進行用戶名密碼的校驗的。
登錄流程
我們打開瀏覽器輸入localhost:8080
由於我們沒有進行登錄,所以會被redirecting
到登錄頁面。我們一起過濾一下控制台信息,抓取到關鍵的信息。
我們看到,這里加載了各種過濾器,當訪問/
時沒發現並沒有登錄,則重定向到默認的/login
頁面,這也是spirng security
的核心。今天重點討論登錄流程,我們先清空控制台,用正確的用戶名和密碼登錄進去。
從控制台我們可以看到很多的過濾器,我們至關注認證流程的一部分,已上圖為准。
1、UsernamePasswordAuthenticationFilter
這理解這個過濾器前,我們先從他的父類AbstractAuthenticationProcessingFilter
入手,既然是過濾器,我們既要入doFilter
入手, 這里是關鍵的流程,子類只是做具體的實現,我們稍后再看
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 請求的轉化
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 關鍵的認證方法,交由子類來實現,我們到子類看
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 返回認證成功
this.successfulAuthentication(request, response, chain, authResult);
}
}
上面的關鍵方法attemptAuthentication(request, response);
在UsernamePasswordAuthenticationFilter
中
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
// 通過“username”拿到用戶名
String username = this.obtainUsername(request);
// 通過"password" 拿到密碼
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 傳入UsernamePasswordAuthenticationToken構造方法,此類是Authentication的子類
// 此時還沒有認證(false)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 交由AuthenticationManager 去處理
return this.getAuthenticationManager().authenticate(authRequest);
}
}
在UsernamePasswordAuthenticationFilter
的關鍵流程中,我們將請求的參數進行符合入參的封裝,
2、AuthenticationManager
AuthenticationManager
本身不包含認證邏輯,其核心是用來管理所有的 AuthenticationProvider
,通過交由合適的 AuthenticationProvider
來實現認證。
3、AuthenticationProvider
Spring Security
支持多種認證邏輯,每一種認證邏輯的認證方式其實就是一種 AuthenticationProvider
。通過 getProviders()
方法就能獲取所有的 AuthenticationProvider
,通過provider.supports()
來判斷 provider 是否支持當前的認證邏輯。
當選擇好一個合適的 AuthenticationProvider
后,通過 provider.authenticate(authentication)
來讓 AuthenticationProvider
進行認證。
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()) {
// 判斷是否是其支持的provider
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
// 由具體的provider去進行處理
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
// 如果還是沒有結果,交由父類在處理一次
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// 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 e) {
lastException = parentException = e;
}
}
if (result != null) {
if (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 than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than 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;
}
4、AbstractUserDetailsAuthenticationProvider
表單登錄的 AuthenticationProvider
主要是由 AbstractUserDetailsAuthenticationProvider
來進行處理的,我們來看下它的 authenticate()
方法。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
// 默認從緩存中去,如果沒有則調用retrieveUser
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
// 校驗密碼等信息
postAuthenticationChecks.check(user);
// 放入緩存
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 認證成功后放入認證成功的信息,里面也是放入傳入UsernamePasswordAuthenticationToken另一個構造方法
return createSuccessAuthentication(principalToReturn, authentication, user);
}
那么關鍵的retrieveUser
里面是什么樣呢?
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 用具體的UserDetailSercvice去獲取用戶信息
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
由於我們的用戶信息是在UserDetailsServiceAutoConfiguration
的配置類中生成了 InMemoryUserDetailsManager
,所以這里的loadUserByUsername
的代碼則是這樣
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
user.isAccountNonExpired(), user.isCredentialsNonExpired(),
user.isAccountNonLocked(), user.getAuthorities());
}
在內存中維護的用戶中去獲取,那么如果是其他的用戶存儲則需要對應的獲取方式,如果是保存在數據庫那么就需要通過sq
l語句去獲取了,感興趣的可以直接點擊JdbcUserDetailsManager
查看相關代碼。
其實真個認證的流程到這里也就結束了,至於成功或失敗后的邏輯最后還是回到了UsernamePasswordAuthenticationFilter
中的結果,如果是成功this.successfulAuthentication(request, response, chain, authResult);
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
// 將認證結果放入到上下文中
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
// 后去的跳轉等信息
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
總結
以上便是spring security
的認證流程,沒想到篇幅會這么長,斷點追蹤的方式很痛苦,大致方向應該是對的,基本的認證流程也應該浮出水面了。本篇主要是從自動配置的方式出發,后續將展示其他的配置方式甚至自定義認證流程,加油!!!
(完)