SpringBoot--- 使用SpringSecurity進行授權認證

凌丹妙藥發表於2020-08-19

SpringBoot--- 使用SpringSecurity進行授權認證

前言

在未接觸 SpringSecurity 、Shiro 等安全認證框架之前,如果有頁面許可權需求需要滿足,通常可以用攔截器,過濾器來實現。

但是,這需要大量配置類去完成,程式碼編寫工作量是巨大的。為提高工作效率,學習SpringSecurity 等框架變得十分必要。

環境

IDEA :2020.1

Maven:3.5.6

SpringBoot: 2.3.2

MySQL 8.0

1、匯入正確的依賴

重要依賴

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
</dependency>

也可以在構建工程師勾選

image-20200818164620611

另外,筆者使用的模板引擎是 Thymeleaf ,因此也需要匯入該依賴,不適用該模板引擎的不需要匯入該依賴。

<!--       thymeleaf-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

2、編寫或匯入頁面素材,HTML頁面等

image-20200818164516869

讀者可以自行編寫,除了login 頁面必須要有 form 表單提交,以便處理登入請求外,其他頁面可根據需要編寫。

關於頁面,登入提交表單有一個注意事項

<form class="form-signin" method="post" action="/login">

一般提交表單,這樣寫是沒有問題的,但是,我們新增了 spring-boot-starter-security 依賴,使用了SpringSecurity ,提交所有表單(包括這次的登入表單),都會交由SpringSecurity 處理。

SpringSecurity 預設開啟了防止跨域攻擊的功能,任何 POST 提交到後臺的表單都要驗證是否帶有 _csrf 引數,一旦傳來的 _csrf 引數不正確,伺服器便返回 403 錯誤;

上述寫法,我們可以訪問後,在除錯模式檢視元素。

image-20200819161740351

是沒有 _csrf 引數的,這樣提交的時候將會被攔截。

image-20200819161835611

提交表單403解決方法

1、直接關閉防止域攻擊功能。(可以在下面介紹到的配置類中使用)
http.csrf().disable()

這樣的做法是不建議的,安全級別會降低。有違使用 SpringSecurity 的初衷。

2、使用 Thymeleaf 在 form 表單新增 th:action 元素,Thymeleaf 會自動為我們新增 _csrf 元素。
<form class="form-signin" method="post" th:action="@{/login}">

image-20200819164101715

3、在 form 表單中手動新增隱藏 _csrf

在 form 表單中手動新增隱藏 _csrf,比較麻煩,這裡不做過多介紹。都用SpringBoot 了,還手動配置這麼多,這不有違初衷了嗎?當然,感興趣的可以自己摸索。

3、測試環境,保證頁面訪問成功

這裡要做的是編寫一個 Controller 類

@Controller
public class RouterController {
    @RequestMapping( {"/","/index"} )
    public String index(){
        return "welcome";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "pages/login";
    }

    @RequestMapping("/level1/{id}")
    public String toLevel1(@PathVariable("id") int id){
        return "pages/level1/"+ id;
    }

    @RequestMapping("/level2/{id}")
    public String toLevel2(@PathVariable("id") int id){
        return "pages/level2/"+ id;
    }
    @RequestMapping("/level3/{id}")
    public String toLevel3(@PathVariable("id") int id){
        return "pages/level3/"+ id;
    }
}

啟動程式,訪問頁面。當然,測試之前,我們需要把 SpringSecurity 的依賴匯入暫時註釋掉,否則,SpringSecurity 將會攔截下我們的請求。

image-20200818171250452

image-20200818181011714

訪問成功,頁面是沒有問題的。這樣做有利於我們後面出問題時,排查問題,並非多此一舉。類似於斷點Debug ,相當於我們在這一階段前的工作是無誤的。問題出現應該在這一斷點(階段)後排查。

別忘了,註釋掉的SpringSecurity ,我們要解除掉註釋。

4、配置使用者,許可權

1、yml

spring:
  security:
    user:
      name: tom001
      password: 1234
      roles: [level1,level2]

這樣就可以配置使用者名稱,密碼和許可權了,太方便了吧!

image-20200818180041123

但是,卻只能新增一個使用者,因為user,password 等屬性都只是String型別的,只有roles 才是List 型別的。筆者內問百度部落格,外問谷歌,Stack Overflow 都沒有找到SpringSecurity可以在yml配置檔案下配置多使用者的方法。如果你知道,請評論留言告訴我,小弟謝過了。

2、配置類

所以最後還是回到配置類上來吧,很多問題還可以從官方和原始碼中找到正確的配置方法。(雖然不能用 yml 提【tou】高【gong】效【jian】率【liao】了 T_T )

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         //定義訪問許可權規則
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("level1")
                .antMatchers("/level2/**").hasRole("level2")
                .antMatchers("/level3/**").hasRole("level3");
        //沒有許可權將跳轉到登入頁面
        http.formLogin();
    }
}

image-20200818181705139

很遺憾,主頁依然可以訪問,在訪問需要許可權的頁面時候,被伺服器拒絕訪問(403 表示伺服器拒絕該訪問請求)。

http.formLogin();

上面這個方法值得我們來分析一下,因為我們 Controller 配置的並沒有 login 而是 toLogin 。

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "pages/login";
    }

SpringSecurity 是如何幫我們自動配置的呢?

我們去到 formLogin() 方法即可一探究竟。

	 * Specifies to support form based authentication. If
	 * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page
	 * will be generated.
	 *
	 * <h2>Example Configurations</h2>
	 *
	 * The most basic configuration defaults to automatically generating a login page at
	 * the URL "/login", redirecting to "/login?error" for authentication failure. The
	 * details of the login page can be found on
	 * {@link FormLoginConfigurer#loginPage(String)}
	 *

在方法體上,找到了註釋。請特別注意以下這句話:

The most basic configuration defaults to automatically generating a login page at
the URL "/login", redirecting to "/login?error" for authentication failure.

預設情況下,最基本的配置是在URL“ /login”處自動生成一個登入頁面,並重定向到“ /login?error”來進行身份驗證失敗。

而且,調轉到的 /login ,並不是我們編寫的 login.html 頁面,而是由 SpringSecurity 提供的登入頁面。

這裡跳轉了很久,難道是 SpringSecurity 在後天寫頁面?哈哈

image-20200819003535923

我們可以點進檢視一下 formLogin() 方法

	 * 	&#064;Override
	 * 	protected void configure(HttpSecurity http) throws Exception {
	 * 		http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin()
	 * 				.usernameParameter(&quot;username&quot;) // default is username
	 * 				.passwordParameter(&quot;password&quot;) // default is password
	 * 				.loginPage(&quot;/authentication/login&quot;) // default is /login with an HTTP get
	 * 				.failureUrl(&quot;/authentication/login?failed&quot;) // default is /login?error
	 * 				.loginProcessingUrl(&quot;/authentication/login/process&quot;); // default is /login
	 * 																		// with an HTTP
	 * 																		// post

原來,我們可以指定登入頁面,SpringSecurity 會幫助我們跳轉過去。

.formLogin().loginPage("/toLogin");

這下幫劉都統接上了腿,一下子就可跳過去了吧?哈哈哈


接下來就是認證方面的工作了。我們需要編寫的類可以通過檢視,需要重寫哪些類,它的引數一般可以標明他要做的配置工作。

既然是配置,那自然是 configure 方法,我們可以去檢視下圖所示的這一 configure方法。

image-20200819005857486

在原始碼,我們發現框架開發者在方法的註釋上,貼心地為我們寫好了配置示例。

	 * &#064;Override
	 * protected void configure(AuthenticationManagerBuilder auth) {
	 * 	auth
	 * 	// enable in memory based authentication with a user named
	 * 	// &quot;user&quot; and &quot;admin&quot;
	 * 	.inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;).and()
	 * 			.withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
	 * }

我們按照他說要求的配置如下:

    //認證
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("tom001").password("1234").roles("level1","level3");
    }

哈哈,終於要完成了,我們來驗證一下吧!

image-20200819010650863

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

密碼沒有編碼?啥意思?

都說了是 SpringSecurity,登入密碼還是明文密碼,那還了得?人家一個反編譯,你豈不是底褲都讓人看光了?

但是官方一句提醒都沒有,啊,這......

確實有點麻煩,但是我們的目的是讓它起作用,還是找找方法吧。

    //認證
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("tom001").password(new BCryptPasswordEncoder().encode("1234")).roles("level1","level3");
    }

最後加上加密類,這次總該成了吧?

image-20200819012942468

成功了!


實際使用中,還是要結合資料庫獲取使用者密碼,許可權等資訊的。

相關文章