您當前的位置:首頁 > 娛樂

Spring Security零基礎入門之二~驗證

作者:由 Alex Wang 發表于 娛樂時間:2018-10-22

1。前言

這是本系列的第二篇文章,上一篇文章主要講了一個入門例子,全系列傳送門如下:

第一篇:Spring Security零基礎入門之一。

第二篇:Spring Security零基礎入門之二~驗證

第三篇:Spring Security零基礎入門之三~使用資料庫進行驗證

第四篇:Spring Security零基礎入門之四~鑑權

Spring Security主要包括兩大功能:

驗證和鑑權

。驗證就是確認使用者的身份,一般採用使用者名稱和密碼的形式;鑑權就是確認使用者擁有的身份(角色、許可權)能否訪問受保護的資源。本文主要講述如何在Spring Security中進行驗證功能的開發,閱讀本文需要的基礎知識:

熟練掌握Java

掌握Spring Boot基礎知識

一點點前端知識包括html/css/javascript

瞭解一點後端框架thymeleaf

2。需求以及示例程式碼

本文是根據第一篇文章進行的改進,在第一篇文章中,實現了以下需求:

網站分為首頁、登入頁、使用者頁面、管理員頁面和報錯頁面;

使用使用者名稱加密碼登入,登入錯誤要報錯;

不同的使用者擁有不同的許可權,不同的許可權可以訪問不同的網頁;

首頁和登入頁不需要任何許可權;

使用者頁面需要USER許可權;

管理員頁面需要ADMIN許可權;

如果使用者沒有登入,則訪問需要許可權的頁面時自動跳轉登入頁面。

為了讓讀者掌握具體的驗證原理、流程和程式碼,本文加入了以下三個功能:

修改使用者名稱密碼的引數名稱

透過自定義一個AuthenticationProvider在系統中加入一個後門

將驗證身份資訊展示到前端

程式碼在我的github中:

該專案一共包含四個可獨立執行的子專案:本文對應的是

normal_security子專案。

從Github下載程式碼後,進入

normal_security

專案直接執行即可觀察效果。

能力強的同學可以直接去看程式碼了,但還是建議先看下面的原理解釋,我將在文章的最後給出程式碼解釋和執行介面展示。

3。原理圖

由於Spring Security本身關於驗證的架構較為複雜,設計的原理、概念、流程和實現程式碼很多,因此先畫了一個思維導圖幫助理解,如下圖所示:

Spring Security零基礎入門之二~驗證

圖1 Spring Security原理圖

各部分內容將在後面一一解釋。

4。參與驗證的要素

Spring Security不僅僅支援網頁登入這一種驗證模式,它還支援多種其他驗證模式。但是其參與驗證的要素基本上還是使用者、密碼、角色、受保護的資源這四種。

使用者名稱稱一般在前端由訪問者填入,而系統使用者在後端一般儲存在記憶體中或資料庫中。

密碼一般在前端由訪問者填入,用於驗證使用者身份,在Security 5。0後密碼必須使用PasswordEncoder加密,一般來說使用BCryptPasswordEncoder即可。

角色,又可以被看做許可權,在Spring Security中,有時用Role表示,有時用Authority表示。前端一般看不到系統的角色。後端角色由管理者賦予給使用者(可以事先賦予,也可以動態賦予),角色資訊一般儲存在記憶體或資料庫中。

受保護的資源,一般來說就是指網址,有時也可以將某些函式方法定義為資源,但本文不涉及這類情況。

參與驗證的要素(使用者名稱、密碼)在前端由表單提交,由網路傳入後端後,會形成一個Authentication類的例項

。(儘管我很討厭直接介紹各種Class和Interface,但是本文不可避免的要涉及到比較多的類,本文會盡量只介紹最重要的)該例項在進行驗證前,攜帶了使用者名稱、密碼等資訊;在驗證成功後,則攜帶了身份資訊、角色等資訊。Authentication介面程式碼節選如下:

public

interface

Authentication

extends

Principal

Serializable

{

Collection

<?

extends

GrantedAuthority

>

getAuthorities

();

Object

getCredentials

();

Object

getDetails

();

Object

getPrincipal

();

boolean

isAuthenticated

();

void

setAuthenticated

boolean

isAuthenticated

throws

IllegalArgumentException

}

其中getCredentials()返回一個Object credentials,它代表驗證憑據,即密碼;getPrincipal()返回一個Object principal,它代表身份資訊,即使用者名稱等;getAuthorities()返回一個Collection<? extends GrantedAuthority>,它代表一組已經分發的許可權,即本次驗證的角色(本文中許可權和角色可以通用)集合。

有了Authentication例項,則驗證流程主要圍繞這個例項來完成。

它會依次穿過整個驗證鏈,並存儲在SecurityContextHolder中。也可以像本文中的程式碼一樣,在驗證途中偽造一個Authentication例項,騙過驗證流程,獲得所有許可權。

5。驗證的規則

驗證規則定義了以下幾個東西:

受保護的資源即網址,它們一般按訪問所需許可權分為幾類

哪一類資源可以由哪些角色訪問

規則定義在WebSecurityConfigurerAdapter的子類中

具體規則的定義方法可以在程式碼中觀察。

6。驗證流程

前文已經介紹了Authentication類,它代表了驗證資訊。

再介紹一個類AuthenticationManager,它是驗證管理類的總介面;而具體的驗證管理需要ProviderManager類,它具有一個List providers屬性,這實際上是一個AuthenticationProvider例項構成的驗證鏈。鏈上都是各種AuthenticationProvider例項,這些例項進行具體的驗證工作,它們之間的關係如下圖(圖來自網際網路)所示:

Spring Security零基礎入門之二~驗證

圖2 驗證管理類關係圖

驗證成功後,驗證例項Authentication會被存入SecurityContextHolder中,而它則利用執行緒本地儲存TLS功能。在驗證成功且驗證未過期的時間段內,驗證會一直有效。而且,可以在需要的地方,從SecurityContextHolder中取出驗證資訊,並進行操作。例如將驗證資訊展示在前端。

具體的驗證流程如下:

後端從前端的表單得到使用者密碼,包裝成一個Authentication類的物件;

將Authentication物件傳給“驗證管理器”ProviderManager進行驗證;

ProviderManager在一條鏈上依次呼叫AuthenticationProvider進行驗證;

驗證成功則返回一個封裝了許可權資訊的Authentication物件(即物件的Collection<? extends GrantedAuthority>屬性被賦值);

將此物件放入安全上下文SecurityContext中;

需要時,可以將Authentication物件從SecurityContextHolder上下文中取出。

注意,在ProviderManager管理的驗證鏈上,任何一個AuthenticationProvider通過了驗證,則驗證成功。所以,要在系統中留一個後門,只需要在程式碼中新增一個AuthenticationProvider的子類BackdoorAuthenticationProvider,並在輸入特定的使用者名稱(alex)時,直接偽造一個驗證成功的Authentication,即可透過驗證,程式碼如下:

@Component

public

class

BackdoorAuthenticationProvider

implements

AuthenticationProvider

{

@Override

public

Authentication

authenticate

Authentication

authentication

throws

AuthenticationException

{

String

name

=

authentication

getName

();

String

password

=

authentication

getCredentials

()。

toString

();

//利用alex使用者名稱登入,不管密碼是什麼都可以,偽裝成admin使用者

if

name

equals

“alex”

))

{

Collection

<

GrantedAuthority

>

authorityCollection

=

new

ArrayList

<>();

authorityCollection

add

new

SimpleGrantedAuthority

“ROLE_ADMIN”

));

authorityCollection

add

new

SimpleGrantedAuthority

“ROLE_USER”

));

return

new

UsernamePasswordAuthenticationToken

“admin”

password

authorityCollection

);

}

else

{

return

null

}

}

@Override

public

boolean

supports

Class

<?>

authentication

{

return

authentication

equals

UsernamePasswordAuthenticationToken

class

);

}

}

然後在SecurityConfiguration類中,將BackdoorAuthenticationProvider的例項加入到驗證鏈中即可,程式碼如下:

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired

BackdoorAuthenticationProvider backdoorAuthenticationProvider;

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

。。。省略

//將自定義驗證類註冊進去

auth。authenticationProvider(backdoorAuthenticationProvider);

}

。。。省略

}

7。程式碼解釋

1)修改使用者名稱密碼的引數名稱

在前端login。html:

<!—— 1。自定義引數名:myusername和mypassword——>

<

div

>

使用者名稱:

<

input

type

=

“text”

name

=

“myusername”

/>

div

>

<

div

>

密碼:

<

input

type

=

“password”

name

=

“mypassword”

/>

div

>

在後端SecurityConfiguration。java:

@Override

protected

void

configure

HttpSecurity

http

throws

Exception

{

http

authorizeRequests

()

antMatchers

“/”

“/index”

“/error”

)。

permitAll

()

antMatchers

“/user/**”

)。

hasRole

“USER”

antMatchers

“/admin/**”

)。

hasRole

“ADMIN”

and

()

formLogin

()。

loginPage

“/login”

)。

defaultSuccessUrl

“/user”

//1。自定義引數名稱,與login。html中的引數對應

usernameParameter

“myusername”

)。

passwordParameter

“mypassword”

and

()

logout

()。

logoutUrl

“/logout”

)。

logoutSuccessUrl

“/login”

);

}

2)透過自定義一個AuthenticationProvider加入一個後門

上一節中已經詳細介紹了這個功能的實現。留下後門後,使用使用者名稱alex,配合任何密碼,都可以成功登陸並獲得管理員許可權,而且登陸後的頁面上顯示的也是admin使用者。

3)將驗證身份資訊展示到前端

前面已經提到,驗證成功後,驗證資訊存入SecurityContextHolder中。因此可以在需要的地方,將其提取出來,然後在前端展示出來。

後端程式碼:

@Controller

public

class

UserController

{

@RequestMapping

“/user”

public

String

user

@AuthenticationPrincipal

Principal

principal

Model

model

{

model

addAttribute

“username”

principal

getName

());

//從SecurityContextHolder中得到Authentication物件,進而獲取許可權列表,傳到前端

Authentication

auth

=

SecurityContextHolder

getContext

()。

getAuthentication

();

Collection

<

GrantedAuthority

>

authorityCollection

=

Collection

<

GrantedAuthority

>)

auth

getAuthorities

();

model

addAttribute

“authorities”

authorityCollection

toString

());

return

“user/user”

}

@RequestMapping

“/admin”

public

String

admin

@AuthenticationPrincipal

Principal

principal

Model

model

{

model

addAttribute

“username”

principal

getName

());

//從SecurityContextHolder中得到Authentication物件,進而獲取許可權列表,傳到前端

Authentication

auth

=

SecurityContextHolder

getContext

()。

getAuthentication

();

Collection

<

GrantedAuthority

>

authorityCollection

=

Collection

<

GrantedAuthority

>)

auth

getAuthorities

();

model

addAttribute

“authorities”

authorityCollection

toString

());

return

“admin/admin”

}

}

前端程式碼:

<

p

>

你的當前使用者名稱是:

p

>

<

p

th:text

=

“${username}”

style

=

“margin-top: 25px; color: crimson”

>

wxb

p

>

<

p

>

你的許可權是:

p

>

<

p

th:text

=

“${authorities}”

style

=

“margin-top: 25px; color: crimson”

>

authorities

p

>

8。小結

關於驗證的內容實在是比較多,因此文字中依然沒有介紹如何利用資料庫中儲存的資訊進行驗證,那將會在下一篇文章中介紹。

好了,可以點讚了,或者到github上star我。