【認證與授權】Spring Security系列之認證流程解析
阿新 • • 發佈:2020-04-27
> 上面我們一起開始了Spring Security的初體驗,並通過簡單的配置甚至零配置就可以完成一個簡單的認證流程。可能我們都有很大的疑惑,這中間到底發生了什麼,為什麼簡單的配置就可以完成一個認證流程啊,可我啥都沒看見,沒有寫頁面,沒有寫介面。這一篇我們將深入到原始碼層面一起來了解一下spring security到底是怎麼工作的。
### 準備工作
在開始原始碼理解前,我們先來做一項基本的準備工作,從日誌中去發現線索,因為我們發現什麼都沒有配置的情況下,他也可以正常的工作,並給我們預置了一個臨時的使用者user。那麼他肯定是在工程啟動的時候做了什麼事情,上一篇我們也提到了是如果生成user使用者和密碼的。這篇我們將仔細的去了解一下。
1、*首先我們配置在`applicaiton.yml`中調整一下日誌級別*
```yml
logging:
level:
org.springframework.security: debug
```
我們將`security`相關的日誌打印出來,一起來啟動或者執行的時候到底發生了什麼。
2、*啟動`spring-security-basic` 工程*
![](https://i.loli.net/2020/04/12/bohgYFAzqynPB2e.gif)
!!!找到了
### 日誌過濾
```
(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` 不管!找到原始碼再說
```java
@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`
```java
@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`
```java
@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 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`
```java
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 1、宣告一個 webSecurity 一起來看一下他是什麼時候初始化的
private WebSecurity webSecurity;
// 2、是否為除錯模式
private Boolean debugEnabled;
private List> webSecurityConfigurers;
private ClassLoader beanClassLoader;
// 3、關鍵點,後置物件處理器,用來初始化物件
@Autowired(required = false)
private ObjectPostProcessor