Spring Security零基礎入門之二~驗證
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本身關於驗證的架構較為複雜,設計的原理、概念、流程和實現程式碼很多,因此先畫了一個思維導圖幫助理解,如下圖所示:
圖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
圖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我。