SpringBoot + Security學習筆記
阿新 • • 發佈:2021-02-19
# SpringSecurity學習筆記
`本以為是總結,最後寫成了筆記,所以還是需要更加的努力啊。`
> 開始的時候看了一遍官方文件,然後只看懂了加密器。
>
> 然後又學了一個尚矽谷的視訊,雖然這個教程是在講一個專案,但我沒有聽懂(應該是我自己的問題)
**程式碼** https://gitee.com/pilearn/learning-spring-security
**中文版文件** https://www.springcloud.cc/spring-security.html
**尚矽谷視訊連結** https://www.bilibili.com/video/BV15a411A7kP
### 什麼是SpringSecurity
Security是Spring全家桶中一個安全框架,他的擴充套件能力非常的強,底層是一條過濾器鏈。通過簡單的配置就可以使用,但通過自己的DIY,可以把每個許可權細化到每個連結上去。
`shiro沒有學,但只推薦學一個安全框架`
這裡搭建的學習專案都是使用SpringBoot
### 獲取SpringSecurity
你可以在maven官網獲取最新版本
```xml
org.springframework.boot
spring-boot-starter-security
2.4.2
```
### 開始一個SpringBoot專案
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.4.2
com.pipihao
securitylearn
0.0.1-SNAPSHOT
securitylearn
Demo project for Spring Boot
1.8
com.alibaba
druid
1.1.21
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
```
### 專案配置檔案
```yml
server:
port: 8001
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
thymeleaf:
cache: false
# 因為Thymeleaf很多有預設配置,所以只關了這個快取,方便重新整理
```
### 資料庫檔案
資料庫版本為 8.0
```mysql
```
### 執行專案
#### 登入
使用者名稱:user
密碼:控制檯輸出的這密碼
### 配置Security
#### 方法一:通過配置檔案修改登入賬號密碼
```yml
spring:
security:
user:
name: xx
password: xx
```
#### 方法二:通過自定義配置SecurityConfig配置類
WebSecurityConfigurerAdapter 類是是Security內建提供了一個預設身份驗證的抽象類,繼承此抽象類實現configure方法則可以對驗證操作實現DIY。[於官方文件 6.3 標題可見]
UserDetailsService介面:查詢資料庫使用者名稱和密碼過程
* 建立類繼承UsernamePasswordAuthenticationFilter,重寫三個方法
*
* 建立類實現UserDetailService,編寫查詢資料過程,返回User物件,這個User物件是安全框架提供物件。
* PasswordEncoder: 資料加密介面,用於返回User物件裡面的密碼加密
#### 方法三:自定義配置類UserDetailsService
### 定義不驗證連結
```java
@Override
protected void configure(HttpSecurity http) throws Exception {
/*
使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。
*/
http
.authorizeRequests()
.antMatchers("/","/no").permitAll() //可以直接訪問的路徑
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html") //配置登入路徑
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/hallo")
.permitAll()
; //設定 登入的網頁
http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了
}
```
### 配置訪問許可權/角色
如果是配置訪問角色則使用是hasRole與hasAnyRole
**這裡非常建議點一下看一下hasRole的原始碼** 使用Role的時候,User的許可權列表是需要加ROLE_字首的
這裡直接使用的是hasAnyAuthority,還有一個方法是hasAuthority
前者可以配置多個許可權,而後者只能配置一個許可權
> 介面只是顯示一個字串
>
> ```java
> @GetMapping("test")
> public String sayTest(){
> return "Test";
> }
> ```
#### SecurityConfig程式碼
```java
@Override
protected void configure(HttpSecurity http) throws Exception {
/*
使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。
*/
http
.authorizeRequests()
.antMatchers("/","/no").permitAll() //可以直接訪問的路徑
.antMatchers("/test").hasAnyAuthority("admin") // 訪問許可權
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html") //配置登入路徑
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/hallo")
.permitAll()
; //設定 登入的網頁
http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了
}
```
#### UserDetailsImpl程式碼
```java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(StringUtils.isEmpty(username)){
throw new RuntimeException("使用者名稱不能為空");
}
IUser iUser= userMapper.getUserByUsername(username);
if(iUser == null){
throw new UsernameNotFoundException("無此使用者");
}
/*此處查詢使用者角色*/
List grantedAuthorityList =
AuthorityUtils.createAuthorityList("admin"); // 許可權的列表
return new User(iUser.getUsername(),bCryptPasswordEncoder.encode(iUser.getPassword()),grantedAuthorityList);
}
```
### 自定義403介面
```
// 在此方法內加上一行 protected void configure(HttpSecurity http)
http.exceptionHandling().accessDeniedPage("/unauth.html");
```
### 許可權註解
#### @Secured
> 判斷是否有角色,這裡匹配的角色需要加字首ROLE_
```java
@GetMapping("update")
@Secured({"ROLE_manager"})
public String update(){
return "update";
}
```
使用其功能時需要在application類上開起
```java
@SpringBootApplication
@MapperScan("com.pipihao.securitylearn.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecuritylearnApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritylearnApplication.class, args);
}
}
```
UserDetailsServiceImpl
```java
List grantedAuthorityList =
AuthorityUtils.createAuthorityList("admin","ROLE_manager");
```
#### @PreAuthorize & @PostAuthorize
此註解即有許可權驗證功能,又有角色驗證功能
```java
@GetMapping("pre1")
@PreAuthorize("hasAnyRole('ROLE_manager')")
public String prePost1(){
return "prePost1";
}
@GetMapping("pre2")
@PreAuthorize("hasAnyAuthority('admin')")
public String prePost2(){
return "prePost2";
}
```
```java
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecuritylearnApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritylearnApplication.class, args);
}
}
```
> @PostAuthorize 與@PreAuthorize的區別就是,Pre會先攔截後執行,而PostAuthorize是先執行,後攔截
>
> 所以我例子中沒有過多的講
#### @PreFilter & @PostFilter
Pre是過濾上傳的資料,Post過濾返回的資料
```java
@GetMapping("list")
@PostFilter("filterObject.username != 'admin' ")
public List list(){
List iUsers = new ArrayList<>();
iUsers.add(new IUser(1,"admin","123"));
iUsers.add(new IUser(2,"user","123"));
return iUsers;
}
// Applicationo類上還是要加上下面這個註解,並設定屬性值
@EnableGlobalMethodSecurity(prePostEnabled = true)
```
效果圖
![image-20210219115204162](https://gitee.com/pimg/image/raw/master/img/image-20210219115204162.png)
上傳則是同理,通過註解寫好判斷,然後測試即可,注:**PreFilter過濾的也只是集合和陣列**
### 使用者登出
```java
/*配置退出登入*/
http.logout().logoutUrl("/logout").logoutSuccessUrl("no").permitAll();
```
登入後,直接通過瀏覽器,訪問此路徑即可(是的,就是如此)
```js
location.href='/logout';
```
### 自動登入
下面是尚矽谷老師寫的原理圖和執行流程
**如果是微服務,則把資料庫改成redis,把cookie改成jwt生成的token**
Security 中的一個類內`JdbcTokenRepositoryImpl`
的常量CREATE_TABLE_SQL
```mysql
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
```
**有興趣的可以看看原始碼** 沒興趣的直接在你使用的資料庫內執行上面這行sql建立一個儲存登入資訊的表
![image-20210219185336468](https://gitee.com/pimg/image/raw/master/img/image-20210219185336468.png)
JdbcTokenRepositoryImpl 是PersistentTokenRepository實現類
下面這種寫那麼應該是多型了
```java
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//jdbcTokenRepository.setCreateTableOnStartup(true); 設定啟動時建立自動登入表
return jdbcTokenRepository;
}
```
**SecurityConfig的方法**
```java
@Override
protected void configure(HttpSecurity http) throws Exception {
/*自定義403連結*/
http.exceptionHandling().accessDeniedPage("/unauth.html");
/*配置退出登入*/
http.logout().logoutUrl("/logout").logoutSuccessUrl("/no").permitAll();
/*
使用and()方法表示關閉XML標記的Java配置,它允許我們繼續配置父標記。如果您閱讀程式碼,它也是有道理的。我想配置授權請求並配置表單登入並配置HTTP基本身份驗證。
*/
http
.authorizeRequests()
.antMatchers("/","/no").permitAll() //可以直接訪問的路徑
.antMatchers("/test").hasAnyAuthority("admin")
.antMatchers("/unauth").hasAnyAuthority("xxx")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html") //配置登入路徑
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/hallo")
.permitAll()
// -------------------就是下面這坨
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) // 自動儲存的時間,秒為單位
.userDetailsService(userDetailsService)
; //設定 登入的網頁
http.csrf().disable(); //如果註釋了這一行,全部要用_csrf的物件來驗證了
}
```
下面是登入介面
```html
user:
pswd:
自動登入
``` 然後在登入的時候打個勾,就可以自動登入了 在DB中會出現如下的資訊 ![image-20210219191716331](https://gitee.com/pimg/image/raw/master/img/image-20210219191716331.png) ### CSRF指令認證 **第一步** 把下面這一行註釋了就開啟了,也就是說他其實是預設開啟的 如果沒有關閉,則會NullPointerException ```java //http.csrf().disable(); ``` Spring Security CSRF 會針對Patch,Post,Put,Delete方法進行防護。(都是一些要更改資料的方法) 系統預設提供了一個csrfToken物件放在HttpSession中,也就是我們所見到了_csrf物件 此物件可以直接使用 開啟CSRF後,則登入的時【POST】,也需要驗證CSRF,而使用HttpSession則需要使用模板引擎,這裡我們使用的是Thymeleaf而非JSP。(大同小異) > 注:使用Thymeleaf的時候,類上的Controller註解不能寫成RestController,不然無法生效的 ```java @Controller public class LoginController { @GetMapping("login") public String login(){ return "login"; } } ``` ```html登入
user:
pswd:
自動登入
``` 切記,預設開了CSRF,則每個表單中應當手動新增一個隱藏域 當Thymeleaf因為你使用了th,則自動給你生成了。 所以 ` th:action="'/doLogin'"` 這樣寫可以省事 如下圖 ![image-20210219193359084](https://gitee.com/pimg/image/raw/master/img/image-20210219193359084.png) ## 總結 本是總結,誰知還是變成了學習筆記。總結代表著會,筆記代表著只能用,說不出什麼名堂。這是看第二遍,當然,這也會像我用正則一樣,每次用正則的時候,都要學一遍正則。 或許SpringSecurity並不難,難的只是步驟有點多。 老師講的很不錯,多聽幾遍就會了。 關於提高技術,應該看文件,把他提供的API都自己看懂。像用Redist代替DB,這樣的微服務中,使用,很有效率。 接下來,我還會繼續學習Security,並出些新筆記,這最多算是一個聽課
pswd:
自動登入
``` 然後在登入的時候打個勾,就可以自動登入了 在DB中會出現如下的資訊 ![image-20210219191716331](https://gitee.com/pimg/image/raw/master/img/image-20210219191716331.png) ### CSRF指令認證 **第一步** 把下面這一行註釋了就開啟了,也就是說他其實是預設開啟的 如果沒有關閉,則會NullPointerException ```java //http.csrf().disable(); ``` Spring Security CSRF 會針對Patch,Post,Put,Delete方法進行防護。(都是一些要更改資料的方法) 系統預設提供了一個csrfToken物件放在HttpSession中,也就是我們所見到了_csrf物件 此物件可以直接使用 開啟CSRF後,則登入的時【POST】,也需要驗證CSRF,而使用HttpSession則需要使用模板引擎,這裡我們使用的是Thymeleaf而非JSP。(大同小異) > 注:使用Thymeleaf的時候,類上的Controller註解不能寫成RestController,不然無法生效的 ```java @Controller public class LoginController { @GetMapping("login") public String login(){ return "login"; } } ``` ```html
pswd:
自動登入
``` 切記,預設開了CSRF,則每個表單中應當手動新增一個隱藏域 當Thymeleaf因為你使用了th,則自動給你生成了。 所以 ` th:action="'/doLogin'"` 這樣寫可以省事 如下圖 ![image-20210219193359084](https://gitee.com/pimg/image/raw/master/img/image-20210219193359084.png) ## 總結 本是總結,誰知還是變成了學習筆記。總結代表著會,筆記代表著只能用,說不出什麼名堂。這是看第二遍,當然,這也會像我用正則一樣,每次用正則的時候,都要學一遍正則。 或許SpringSecurity並不難,難的只是步驟有點多。 老師講的很不錯,多聽幾遍就會了。 關於提高技術,應該看文件,把他提供的API都自己看懂。像用Redist代替DB,這樣的微服務中,使用,很有效率。 接下來,我還會繼續學習Security,並出些新筆記,這最多算是一個聽課