手摸手教你定制 Spring Security 表單登錄
在本專欄前篇文章中介紹了HttpBasic模式,該模式比較簡(jiǎn)單,只是進(jìn)行了通過(guò)攜帶Http的Header進(jìn)行簡(jiǎn)單的登錄驗(yàn)證,而且沒(méi)有可以定制的登錄頁(yè)面,所以使用場(chǎng)景比較窄。
對(duì)于一個(gè)完整的應(yīng)用系統(tǒng),與登錄驗(yàn)證相關(guān)的頁(yè)面都是高度定制化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定制登錄頁(yè)面,也就是本文給大家介紹的FormLogin模式登錄認(rèn)證模式。
1. 新建項(xiàng)目
在介紹相關(guān)內(nèi)容之前,需要先搭建一個(gè)demo,新建一個(gè)項(xiàng)目spring-security-02,需要添加依賴如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
除此之外其實(shí)還需要添加web、thymeleaf的依賴,這里就不在貼出來(lái)了。
demo結(jié)構(gòu)如下:
2. 新建登錄頁(yè)面
這里不再使用Security默認(rèn)的頁(yè)面,自己定制一個(gè),代碼如下:
單純的一個(gè)表單登錄頁(yè)面,需要注意以下幾個(gè)參數(shù):
- action:security登錄的url,可以自定義。
- username:security登錄的用戶名,可以自定義。
- password:security登錄的密碼,可以自定義。
以上三個(gè)參數(shù)都可以在security通過(guò)配置的方式定義。
3. 新建首頁(yè)
這個(gè)是登錄成功后跳轉(zhuǎn)的首頁(yè),代碼如下:
4. 新建接口
在security中一切的接口都稱之為資源,下面新建兩個(gè)測(cè)試接口,代碼如下:
5. formLogin配置
在介紹如何配置之前,先來(lái)看下formLogin模式登錄的5個(gè)要素:
- 登錄認(rèn)證邏輯-登錄URL:這個(gè)URL在security中默認(rèn)是/login且POST請(qǐng)求,但是也可以通過(guò)配置自定義。
- 如何接收登錄參數(shù):用戶名、密碼默認(rèn)接收的字段分別是username、password,同樣也是可以通過(guò)配置自定義。
- 登陸成功后邏輯:登錄成功后的處理邏輯,比如跳轉(zhuǎn)到指定的頁(yè)面、返回特定的JSON數(shù)據(jù),這個(gè)也是可以定制。
- 資源訪問(wèn)控制規(guī)則:這個(gè)用于控制什么用戶、什么角色可以訪問(wèn)什么資源,可以靜態(tài)指定也可以從數(shù)據(jù)庫(kù)中加載。
- 用戶具有角色權(quán)限:配置某個(gè)用戶擁有什么角色、擁有什么權(quán)限,可以靜態(tài)指定也可以從數(shù)據(jù)庫(kù)中加載。
一般來(lái)說(shuō),使用權(quán)限認(rèn)證框架的的業(yè)務(wù)系統(tǒng)登錄驗(yàn)證邏輯是固定的,而資源訪問(wèn)控制規(guī)則和用戶信息是從數(shù)據(jù)庫(kù)或其他存儲(chǔ)介質(zhì)靈活加載的。但本文所有的用戶、資源、權(quán)限信息都是代碼配置寫死的,旨在為大家介紹formLogin認(rèn)證模式,如何從數(shù)據(jù)庫(kù)加載權(quán)限認(rèn)證相關(guān)信息我還會(huì)結(jié)合RBAC權(quán)限模型再寫文章的。
針對(duì)上述5個(gè)的要素,formLogin配置代碼如下:
首先,我們要繼承WebSecurityConfigurerAdapte?r ,重寫configure(HttpSecurity http) 方法,該方法用來(lái)配置登錄驗(yàn)證邏輯。請(qǐng)注意看代碼中的注釋信息。
上述代碼分為兩個(gè)部分:
第一部分是formLogin配置段,用于配置登錄驗(yàn)證邏輯相關(guān)的信息。如:登錄頁(yè)面、登錄成功頁(yè)面、登錄請(qǐng)求處理路徑等。
- .loginPage("/login/page")?:指定的第2步定制的登錄頁(yè)面,需要寫個(gè)mvc接口跳轉(zhuǎn)到login.html,見(jiàn)源碼。
- .loginProcessingUrl("/login")?:指定處理登錄的邏輯的url,這個(gè)接口不需要開(kāi)發(fā)者定義,security中通過(guò)過(guò)濾器。UsernamePasswordAuthenticationFilter處理,后文介紹。
- .usernameParameter("username")?:指定用戶名的接收參數(shù)的字段,默認(rèn)是username,具體邏輯在UsernamePasswordAuthenticationFilter。
- .passwordParameter("password")?:指定密碼的接收參數(shù)的字段,默認(rèn)是username,具體邏輯在UsernamePasswordAuthenticationFilter。
- .defaultSuccessUrl("/")?:登錄認(rèn)證成功后默認(rèn)轉(zhuǎn)跳的路徑,這里/?則是跳轉(zhuǎn)到/index.html,可以自定義。
- .failureUrl("/login/page"):登陸失敗的跳轉(zhuǎn)的路徑。
第二部分是authorizeRequests配置段,用于配置資源的訪問(wèn)控制規(guī)則
- .antMatchers("/login/page","/login").permitAll():配置登錄頁(yè)面、登錄接口直接放行,不需要攔截登錄
- .antMatchers("/","/hello1").hasAnyAuthority("ROLE_user","ROLE_admin")?:設(shè)置/hello1、/?這兩個(gè)資源需要user和admin的角色才可以訪問(wèn)。
- .antMatchers("/hello2").hasAnyRole("admin")?:配置/hello2?這個(gè)資源需要admin的角色才可以訪問(wèn)。
- .anyRequest().authenticated():除了上面的配置的規(guī)則,訪問(wèn)其他的資源都需要登錄認(rèn)證通過(guò)才可以訪問(wèn)。
6. 用戶、角色配置
在上述的規(guī)則中配置了一些資源需要特定的角色才可以訪問(wèn),比如user、admin,那么這些角色如何去指定呢?
在security中提供了配置的方式,代碼如下:
上述的代碼配置很簡(jiǎn)單,創(chuàng)建了兩個(gè)用戶且指定了角色,分別如下:
- user:密碼123456,賦予的角色為user。
- admin:密碼123456,賦予的角色為user、admin。
配置解釋如下:
- .inMemoryAuthentication():指的是在內(nèi)存里面存儲(chǔ)用戶的身份認(rèn)證和授權(quán)信息;這里還可以配置從數(shù)據(jù)庫(kù)中動(dòng)態(tài)加載,后文介紹。
- withUser("user"):用戶名是user。
- password(passwordEncoder().encode("123456")):密碼是加密之后的123456。
- roles():方法用于指定用戶的角色,一個(gè)用戶可以有多個(gè)角色。
- passwordEncoder(passwordEncoder())?:指定密碼的加密方式,使用的是BCryptPasswordEncoder,后文介紹。
7. 簡(jiǎn)單測(cè)試
按照上述6個(gè)步驟基本實(shí)現(xiàn)了一個(gè)表單登錄,下面測(cè)試一下。
瀏覽器訪問(wèn)http://localhost:8081/hello2,第一次訪問(wèn)由于未登錄會(huì)自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面,如下圖:
輸入用戶名和密碼,由于/hello2?這個(gè)資源需要admin?的角色才能訪問(wèn),因此必須用admin這個(gè)用戶登錄,否則將會(huì)報(bào)403的錯(cuò)誤,登錄成功后將能夠正常訪問(wèn)。
如果用戶名或者密碼錯(cuò)誤將會(huì)觸發(fā).failureUrl("/login/page")這個(gè)配置,自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面
8. 自定義登錄結(jié)果
在第5步的配置中,和登錄結(jié)果相關(guān)的配置有如下兩個(gè):
- .defaultSuccessUrl("/")?:登錄認(rèn)證成功后默認(rèn)轉(zhuǎn)跳的路徑,這里/?則是跳轉(zhuǎn)到/index.html,可以自定義。
- .failureUrl("/login/page"):登陸失敗的跳轉(zhuǎn)的路徑。
這兩個(gè)配置都是指定URL的方式:
- 當(dāng)我們登錄成功的時(shí)候,是由AuthenticationSuccessHandler?進(jìn)行登錄結(jié)果處理,默認(rèn)跳轉(zhuǎn)到defaultSuccessUrl配置的路徑對(duì)應(yīng)的資源頁(yè)面(一般是首頁(yè)index.html)。
- 當(dāng)我們登錄失敗的時(shí)候,是由AuthenticationfailureHandler?進(jìn)行登錄結(jié)果處理,默認(rèn)跳轉(zhuǎn)到failureUrl配置的路徑對(duì)應(yīng)的資源頁(yè)面(一般也是跳轉(zhuǎn)登錄頁(yè)login.html,重新登錄)。
但是在web應(yīng)用開(kāi)發(fā)過(guò)程中需求是千變?nèi)f化的,有時(shí)需要我們針對(duì)登錄結(jié)果做個(gè)性化處理,比如:
- 我們希望不同的人登陸之后,看到不同的首頁(yè)(及向不同的路徑跳轉(zhuǎn))。
- 我們應(yīng)用是前后端分離的,驗(yàn)證響應(yīng)結(jié)果是JSON格式數(shù)據(jù),而不是頁(yè)面跳轉(zhuǎn)。
- …… 其他未盡的例子。
因此需要自定義的登錄結(jié)果,這篇文章先介紹如何定制跳轉(zhuǎn)頁(yè)面,關(guān)于JSON格式數(shù)據(jù)就是前后端分離架構(gòu)下需要用到。
8.1 自定義登錄成功結(jié)果
AuthenticationSuccessHandler?接口是Security提供的認(rèn)證成功處理器接口,我們只需要去實(shí)現(xiàn)它即可。但是通常來(lái)說(shuō),我們不會(huì)直接去實(shí)現(xiàn)AuthenticationSuccessHandler?接口,而是繼承SavedRequestAwareAuthenticationSuccessHandler? 類,這個(gè)類會(huì)記住用戶上一次請(qǐng)求的資源路徑,比如/hello2?這個(gè)路徑,登錄成功后將會(huì)自動(dòng)跳轉(zhuǎn)到/hello2這個(gè)頁(yè)面而不是首頁(yè)。
代碼如下:
8.2 自定義登錄失敗結(jié)果
這里我們同樣沒(méi)有直接實(shí)現(xiàn)AuthenticationFailureHandler?接口,而是繼承SimpleUrlAuthenticationFailureHandler 類。該類中默認(rèn)實(shí)現(xiàn)了登錄驗(yàn)證失敗的跳轉(zhuǎn)邏輯,即登陸失敗之后回到登錄頁(yè)面。我們可以利用這一點(diǎn)簡(jiǎn)化我們的代碼。
代碼如下:
8.3 SecurityConfig中配置
配置如下:
將自定義的AuthenticationSuccessHandler和AuthenticationFailureHandler注入到Spring Security配置類中
使用formlogin模式,配置successHandler和failureHandler。
不要配置defaultSuccessUrl和failureUrl,否則自定義handler將失效。handler配置與URL配置只能二選一
總結(jié)
本篇文章介紹了Spring Security 的 formLogin的配置方式,需要注意的是這里不支持前后端分離架構(gòu),