Spring Security入門demo案例
一、簡(jiǎn)介
Spring Security是一個(gè)高度自定義的安全框架。利用Spring IoC/DI和AOP功能,為系統(tǒng)提供了聲明式安全訪問控制功能,減少了為系統(tǒng)安全而編寫大量重復(fù)代碼的工作。主要包含如下幾個(gè)重要的內(nèi)容:
- 認(rèn)證(Authentication),系統(tǒng)認(rèn)為用戶是否能登錄。
- 授權(quán)(Authorization),系統(tǒng)判斷用戶是否有權(quán)限去做某些事情。
二、入門案例
首先引入必要的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后創(chuàng)建一個(gè)controller:
@Slf4j
@RestController
public class SecurityController {
@GetMapping({"/", "/index"})
public String getLogin() {
log.info("進(jìn)入index");
return "index";
}
}
此時(shí),我們的入門案例就完成了。啟動(dòng)項(xiàng)目,Spring Security默認(rèn)就開啟了,此時(shí)訪問localhost:8080/index就會(huì)被Spring Security攔截,跳轉(zhuǎn)到內(nèi)置的登錄頁(yè)面要求登錄。
默認(rèn)情況下,登錄的用戶名為user,密碼在啟動(dòng)項(xiàng)目的時(shí)候,控制臺(tái)有打印出來:
Using generated security password: 0bfad04b-7a47-40fb-ae15-2a4a7c57099b
使用如上的賬密登錄后,再次訪問localhost:8080/index就可以正常返回預(yù)期的內(nèi)容index了。
如果我們不希望使用默認(rèn)的用戶密碼,可以在配置文件中指定一個(gè),如此Spring Security就會(huì)使用我們指定的,而不會(huì)使用默認(rèn)的了。
spring.security.user.name=zhangxun spring.security.user.password=123123
三、自定義認(rèn)證邏輯
當(dāng)我們開啟自定義認(rèn)證邏輯后,上面的默認(rèn)用戶和配置文件中的用戶就不生效了,可以先行刪除。
我們新建一個(gè)MySecurityConfig類,并繼承WebSecurityConfigurerAdapter類,用于自定義認(rèn)證邏輯。
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 指定使用BCryptPasswordEncoder對(duì)前端傳過來的明文密碼進(jìn)行encode
PasswordEncoder encoder = new BCryptPasswordEncoder();
// 用戶的真實(shí)密碼需要encode,security是比較兩個(gè)密文是否一致
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("root").password(encoder.encode("root123")).roles();
}
}
需要注意的是,密碼必須使用如上的PasswordEncoder進(jìn)行編碼,否則會(huì)拋出如下錯(cuò)誤:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
此時(shí),我們重啟應(yīng)用,訪問localhost:8080/index進(jìn)入內(nèi)置的登錄頁(yè)面,輸入root/root123之后就能正常返回index內(nèi)容了。
四、自定義授權(quán)邏輯
一般權(quán)限管理都是基于RBAC模型的,即登錄的用戶肯定擁有某些角色,這些角色允許訪問某些資源。我們先來改造下認(rèn)證的邏輯:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("root").password(encoder.encode("root123")).roles("admin","manager")
.and()
.withUser("manager").password(encoder.encode("mm000")).roles("manager");
}
使得root用戶擁有admin和manager兩個(gè)角色,zhang用戶擁有manager一個(gè)角色。
然后我們?cè)谠撆渲妙愔性僭黾幼远x授權(quán)的邏輯:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 任何角色允許訪問
.antMatchers("/", "/index").permitAll()
// 僅admin角色可以訪問
.antMatchers("/admin/**").hasRole("admin")
// admin和manager兩個(gè)角色可以訪問
.antMatchers("/manager/**").hasAnyRole("admin", "manager");
// 沒有權(quán)限則進(jìn)入內(nèi)置的登錄頁(yè)面
http.formLogin();
}
然后為了測(cè)試,我們還需要增加幾個(gè)資源:
@Slf4j
@RestController
public class SecurityController {
@GetMapping({"/", "/index"})
public String getLogin() {
log.info("進(jìn)入index");
return "index";
}
@GetMapping("admin/getHello")
public String getAdminHello(){
return "hello admin!";
}
@GetMapping("manager/getHello")
public String getManagerHello(){
return "hello manager!";
}
@GetMapping("guest/getHello")
public String getGuestHello(){
return "hello guest!";
}
}
此時(shí),重啟項(xiàng)目,我們發(fā)現(xiàn):
- 訪問/,/index,/guest/**的資源直接就能返回,不需要認(rèn)證和授權(quán)。
- 訪問/admin/**資源的時(shí)候,由于沒有登錄,會(huì)跳轉(zhuǎn)到內(nèi)置的登錄頁(yè)面;如果已經(jīng)登錄,只有root用戶登錄后才可以訪問;
- 訪問/admin/**資源的時(shí)候,由于沒有登錄,會(huì)跳轉(zhuǎn)到內(nèi)置的登錄頁(yè)面;如果已經(jīng)登錄,那么root和zhang用戶都能訪問;
我們還可以定制自己的登錄頁(yè)面,用于替換Spring Security內(nèi)置的登錄頁(yè)面,這塊需要定制html頁(yè)面,本文不再詳述,比較簡(jiǎn)單,可以參考formLogin的源碼注釋,里面講的比較清楚。
五、注銷登錄
因?yàn)槲覀兪褂玫氖荢pring Security內(nèi)置的登錄頁(yè)面,各個(gè)資源返回的也是json字符串,并非頁(yè)面,所以如何實(shí)現(xiàn)注銷登錄是個(gè)問題。但可以通過閱讀HttpSecurity:logout中的源碼注釋,我們基本就能學(xué)會(huì)怎么操作了。
- 注銷登錄默認(rèn)就開啟了,默認(rèn)是訪問/logout,和/login一樣都是Spring Security自己實(shí)現(xiàn)的,我們調(diào)用即可;
- 注銷登錄會(huì)清除服務(wù)器端的session,清除remember me等設(shè)置;這個(gè)后面再詳細(xì)解說;
- 注銷登錄后默認(rèn)會(huì)跳轉(zhuǎn)到/login頁(yè)面;
還是如上的案例,我們?cè)诘卿浐螅苯诱{(diào)用http://localhost:8080/logout就可以實(shí)現(xiàn)上述的注銷登錄功能了。
但是在有些時(shí)候,我們會(huì)自定義登出的URL以及成功登出后應(yīng)該跳轉(zhuǎn)到哪個(gè)URL,Spring Security也支持我們進(jìn)行自定義。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 任何角色允許訪問
.antMatchers("/", "/index").permitAll()
// 僅admin角色可以訪問
.antMatchers("/admin/**").hasRole("admin")
// admin和manager兩個(gè)角色可以訪問
.antMatchers("/manager/**").hasAnyRole("admin", "manager");
// 沒有權(quán)限進(jìn)入內(nèi)置的登錄頁(yè)面
http.formLogin();
// 自定義登出邏輯
http.logout().logoutUrl("/myLogOut").logoutSuccessUrl("/index");
}
當(dāng)Post方式請(qǐng)求/myLogOut的時(shí)候就會(huì)觸發(fā)Spring Security的登出邏輯,并且登出后會(huì)跳轉(zhuǎn)到/index界面。
注意:在本案例中,是使用瀏覽器進(jìn)行測(cè)試的,而且沒有html的頁(yè)面,所以使用瀏覽器發(fā)起post請(qǐng)求比較困難,那么使用get請(qǐng)求發(fā)起可以嗎?默認(rèn)是不行的,因?yàn)镾pring Security默認(rèn)開啟了CSRF校驗(yàn),所有改變狀態(tài)的請(qǐng)求都必須以POST方式提交,為了能驗(yàn)證我們這個(gè)例子,我們需要把CSRF校驗(yàn)關(guān)掉,即在如上logout代碼后面加上如下的配置:
// 暫時(shí)關(guān)閉CSRF校驗(yàn),允許get請(qǐng)求登出 http.csrf().disable();
此時(shí)再重啟應(yīng)用,就可以驗(yàn)證localhost:8080/myLogOut的登出邏輯了。
六、記住我功能
當(dāng)我們沒有開啟記住我功能的時(shí)候,登錄root用戶后,如果關(guān)掉瀏覽器,重新打開網(wǎng)址,會(huì)發(fā)現(xiàn)登錄已經(jīng)退出了,這是因?yàn)榈卿浶畔⒅辉诋?dāng)前會(huì)話有效。
如果我們想要在某個(gè)時(shí)間段以內(nèi),一直使root用戶處于登錄狀態(tài),那么就需要在瀏覽器端設(shè)置一個(gè)cookie,在有效期內(nèi),這個(gè)cookie所屬的用戶就一直是登錄的狀態(tài)。同樣的,只要在上面注銷登錄的代碼后面加上:
// 開啟remember me功能,有效期默認(rèn)14天 http.rememberMe();
此時(shí)內(nèi)置的登錄頁(yè)面會(huì)出現(xiàn)記住我的選擇框,當(dāng)我們選擇上登錄后,瀏覽器端就會(huì)有當(dāng)前用戶的cookie信息了(名稱為remember-me),在它過期之前,登錄狀態(tài)就一直有效。
需要用戶主動(dòng)退出登錄,也就是調(diào)用我們上面的/myLogOut才能將cookie清除并退出登錄。
如果是自定義的登錄頁(yè)面,可以在后面鏈?zhǔn)秸{(diào)用rememberMeParameter()方法,傳入自己的rememberme參數(shù)名稱即可。
以上是關(guān)于Spring Security的基本使用方法,使用數(shù)據(jù)庫(kù)及其它特性將會(huì)在后面的文章中予以說明。
七、會(huì)話管理
在以上例子中,認(rèn)證和授權(quán)都是Spring Security自動(dòng)進(jìn)行的。但是有的時(shí)候我們需要管理會(huì)話,比如從會(huì)話中獲取用戶姓名、用戶的權(quán)限信息;會(huì)話策略選擇以及會(huì)話超時(shí)設(shè)置等。
我們只需要增加如下的方法即可:
private String getName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if(principal == null){
return "游客";
}
if(principal instanceof UserDetails){
UserDetails userDetails = (UserDetails) principal;
return userDetails.getUsername();
} else{
return principal.toString();
}
}
該方法使用SecurityContextHolder獲取上下文信息,然后再獲取到其中的用戶名即可,當(dāng)然其中還提供了可以獲取密碼、權(quán)限信息等方法。
Session的管理策略有以下幾種:
- always,如果沒有Session就會(huì)創(chuàng)建一個(gè);
- ifRequired,登錄時(shí)如果有需要,就創(chuàng)建一個(gè);
- never,不會(huì)主動(dòng)創(chuàng)建session,如果其它地方創(chuàng)建了session,就會(huì)使用它;
- stateless,不會(huì)創(chuàng)建也不會(huì)使用session;
其中ifRequired是默認(rèn)的模式,stateless是采用token機(jī)制時(shí),session禁用的模式,設(shè)置方法如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
至于session的超時(shí)和安全可以在配置文件中設(shè)置:
# 超時(shí)時(shí)間設(shè)置 server.servlet.session.timeout=3600s # 瀏覽器腳本將無法訪問cookie server.servlet.session.cookie.http‐only=true # cookie將僅通過HTTPS連接發(fā)送 server.servlet.session.cookie.secure=true
到此這篇關(guān)于Spring Security入門demo案例的文章就介紹到這了,更多相關(guān)Spring Security入門內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編譯時(shí)類型與運(yùn)行時(shí)類型
這篇文章主要介紹了Java編譯時(shí)類型與運(yùn)行時(shí)類型,文章以父類BaseClass和子類SubClass為例展開對(duì)主題的探討,具有一的?參考價(jià)值,需要的小伙伴可以參考一下2022-03-03
Spring使用RestTemplate和Junit單元測(cè)試的注意事項(xiàng)
這篇文章主要介紹了Spring使用RestTemplate和Junit單元測(cè)試的注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot自定義starter方法及注解實(shí)例
這篇文章主要為大家介紹了springboot自定義starter方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
如何解決redis的NOAUTH Authentication required異常
這篇文章主要介紹了Jedis異常解決:NOAUTH Authentication required,,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2019-07-07
Java利用jenkins做項(xiàng)目的自動(dòng)化部署
這篇文章主要介紹了Java利用jenkins做項(xiàng)目的自動(dòng)化部署,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
Java使用Filter實(shí)現(xiàn)登錄驗(yàn)證
本文主要介紹了Java使用Filter實(shí)現(xiàn)登錄驗(yàn)證,Filter類似于門衛(wèi),你在進(jìn)入之前門衛(wèi)需要盤查你,身份合法進(jìn)入,身份不合法攔截,感興趣的可以了解一下2023-11-11
java全角與半角標(biāo)點(diǎn)符號(hào)相互轉(zhuǎn)換詳解
這篇文章主要為大家介紹了java全角與半角標(biāo)點(diǎn)符號(hào)相互轉(zhuǎn)換詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

