SpringSecurity
安全簡介
1运沦、在 Web 開發(fā)中狐树,安全一直是非常重要的一個方面。安全雖然屬于應(yīng)用的非功能性需求忆家,但是應(yīng)該在應(yīng)用開發(fā)的初期就考慮進來。如果在應(yīng)用開發(fā)的后期才考慮安全的問題德迹,就可能陷入一個兩難的境地:一方面芽卿,應(yīng)用存在嚴(yán)重的安全漏洞,無法滿足用戶的要求胳搞,并可能造成用戶的隱私數(shù)據(jù)被攻擊者竊刃独称杨;另一方面,應(yīng)用的基本架構(gòu)已經(jīng)確定筷转,要修復(fù)安全漏洞姑原,可能需要對系統(tǒng)的架構(gòu)做出比較重大的調(diào)整,因而需要更多的開發(fā)時間呜舒,影響應(yīng)用的發(fā)布進程锭汛。因此,從應(yīng)用開發(fā)的第一天就應(yīng)該把安全相關(guān)的因素考慮進來阴绢,并在整個應(yīng)用的開發(fā)過程中店乐。
2、市面上存在比較有名的:Shiro呻袭,Spring Security !
3腺兴、這里需要闡述一下的是左电,每一個框架的出現(xiàn)都是為了解決某一問題而產(chǎn)生了,那么Spring Security框架的出現(xiàn)是為了解決什么問題呢页响?
4篓足、首先我們看下它的官網(wǎng)介紹:Spring Security官網(wǎng)地址
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
5、Spring Security是一個功能強大且高度可定制的身份驗證和訪問控制框架闰蚕。它實際上是保護基于spring的應(yīng)用程序的標(biāo)準(zhǔn)栈拖。
6、Spring Security是一個框架没陡,側(cè)重于為Java應(yīng)用程序提供身份驗證和授權(quán)涩哟。與所有Spring項目一樣,Spring安全性的真正強大之處在于它可以輕松地擴展以滿足定制需求
7盼玄、從官網(wǎng)的介紹中可以知道這是一個權(quán)限框架贴彼。想我們之前做項目是沒有使用框架是怎么控制權(quán)限的?對于權(quán)限 一般會細分為功能權(quán)限埃儿,訪問權(quán)限器仗,和菜單權(quán)限。代碼會寫的非常的繁瑣童番,冗余精钮。
8、怎么解決之前寫權(quán)限代碼繁瑣剃斧,冗余的問題轨香,一些主流框架就應(yīng)運而生而Spring Scecurity就是其中的一種。
9悯衬、Spring 是一個非常流行和成功的 Java 應(yīng)用開發(fā)框架弹沽。Spring Security 基于 Spring 框架檀夹,提供了一套 Web 應(yīng)用安全性的完整解決方案。一般來說策橘,Web 應(yīng)用的安全性包括用戶認證(Authentication)和用戶授權(quán)(Authorization)兩個部分炸渡。
- 用戶認證指的是驗證某個用戶是否為系統(tǒng)中的合法主體,也就是說用戶能否訪問該系統(tǒng)丽已。用戶認證一般要求用戶提供用戶名和密碼蚌堵。系統(tǒng)通過校驗用戶名和密碼來完成認證過程。
- 用戶授權(quán)指的是驗證某個用戶是否有權(quán)限執(zhí)行某個操作沛婴。在一個系統(tǒng)中吼畏,不同用戶所具有的權(quán)限是不同的。比如對一個文件來說嘁灯,有的用戶只能進行讀取泻蚊,而有的用戶可以進行修改。一般來說丑婿,系統(tǒng)會為不同的用戶分配不同的角色性雄,而每個角色則對應(yīng)一系列的權(quán)限。
10羹奉、對于上面提到的兩種應(yīng)用情景秒旋,Spring Security 框架都有很好的支持。
- 在用戶認證方面诀拭,Spring Security 框架支持主流的認證方式迁筛,包括 HTTP 基本認證、HTTP 表單驗證耕挨、HTTP 摘要認證哀托、OpenID 和 LDAP 等迄靠。
- 在用戶授權(quán)方面撇吞,Spring Security 提供了基于角色的訪問控制和訪問控制列表(Access Control List浮声,ACL),可以對應(yīng)用中的領(lǐng)域?qū)ο筮M行細粒度的控制赋铝。
實戰(zhàn)測試
實驗環(huán)境搭建
新建一個初始的springboot項目web模塊插勤,thymeleaf模塊
-
導(dǎo)入靜態(tài)資源
-
controller跳轉(zhuǎn)!
package nuc.ss.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class RouterController { @RequestMapping({"/","/index"}) public String index() { return "index"; } @RequestMapping("/toLogin") public String toLogin() { return "views/login"; } @RequestMapping("/level1/{id}") public String level1(@PathVariable("id") int id) { return "views/level1/" + id; } @RequestMapping("/level2/{id}") public String level2(@PathVariable("id") int id) { return "views/level2/" + id; } @RequestMapping("/level3/{id}") public String level3(@PathVariable("id") int id) { return "views/level3/" + id; } }
-
測試實驗環(huán)境是否OK革骨!
首頁
登錄
詳情
認識SpringSecurity
Spring Security 是針對Spring項目的安全框架农尖,也是Spring Boot底層安全模塊默認的技術(shù)選型,他可以實現(xiàn)強大的Web安全控制良哲,對于安全控制盛卡,我們僅需要引入 spring-boot-starter-security 模塊,進行少量的配置筑凫,即可實現(xiàn)強大的安全管理滑沧!
記住幾個類:
-
WebSecurityConfigurerAdapter
:自定義Security策略 -
AuthenticationManagerBuilder
:自定義認證策略 -
@EnableWebSecurity
:開啟WebSecurity模式
Spring Security的兩個主要目標(biāo)是 “認證” 和 “授權(quán)”(訪問控制)并村。
“認證”(Authentication)
身份驗證是關(guān)于驗證您的憑據(jù),如用戶名/用戶ID和密碼滓技,以驗證您的身份哩牍。
身份驗證通常通過用戶名和密碼完成,有時與身份驗證因素結(jié)合使用令漂。
“授權(quán)” (Authorization)
授權(quán)發(fā)生在系統(tǒng)成功驗證您的身份后膝昆,最終會授予您訪問資源(如信息,文件叠必,數(shù)據(jù)庫荚孵,資金,位置纬朝,幾乎任何內(nèi)容)的完全權(quán)限收叶。
這個概念是通用的,而不是只在Spring Security 中存在玄组。
認證和授權(quán)
目前滔驾,我們的測試環(huán)境,是誰都可以訪問的俄讹,我們使用 Spring Security 增加上認證和授權(quán)的功能
-
引入 Spring Security 模塊
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
編寫 Spring Security 配置類
參考官網(wǎng):https://spring.io/projects/spring-security
查看我們自己項目中的版本,找到對應(yīng)的幫助文檔:https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5
-
servlet-applications 8.16.4
@EnableWebSecurity public class Config extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .apply(customDsl()) .flag(true) .and() ...; } }
-
編寫基礎(chǔ)配置類
package nuc.ss.config; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity// 開啟WebSecurity模式 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); } }
-
定制請求的授權(quán)規(guī)則
看源碼
仿寫
//鏈?zhǔn)骄幊?@Override protected void configure(HttpSecurity http) throws Exception { // 首頁所有人都可以訪問绕德,功能也只有對應(yīng)有權(quán)限的人才能訪問到 // 請求授權(quán)的規(guī)則 http.authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3"); }
-
測試一下:發(fā)現(xiàn)除了首頁都進不去了患膛!因為我們目前沒有登錄的角色,因為請求需要登錄的角色擁有對應(yīng)的權(quán)限才可以耻蛇!
-
在configure()方法中加入以下配置踪蹬,開啟自動配置的登錄功能!
// 開啟自動配置的登錄功能 // /login 請求來到登錄頁 // /login?error 重定向到這里表示登錄失敗 http.formLogin();
-
測試一下:發(fā)現(xiàn)臣咖,沒有權(quán)限的時候跃捣,會跳轉(zhuǎn)到登錄的頁面!
-
查看剛才登錄頁的注釋信息夺蛇;
我們可以定義認證規(guī)則疚漆,重寫configure的另一個方法
源碼:
仿寫
// 認證,springboot 2.1.x 可以直接使用 // 密碼編碼: PasswordEncoder @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //這些數(shù)據(jù)正常應(yīng)該中數(shù)據(jù)庫中讀 auth.inMemoryAuthentication() .withUser("kuangshen").password("123456").roles("vip2","vip3") .and() .withUser("root").password("123456").roles("vip1","vip2","vip3") .and() .withUser("guest").password("123456").roles("vip1"); }
-
測試刁赦,我們可以使用這些賬號登錄進行測試娶聘!發(fā)現(xiàn)會報錯!
There is no PasswordEncoder mapped for the id “null”
-
原因甚脉,我們要將前端傳過來的密碼進行某種方式加密丸升,否則就無法登錄,修改代碼
// 認證牺氨,springboot 2.1.x 可以直接使用 // 密碼編碼: PasswordEncoder // 在spring Secutiry 5.0+ 新增了很多加密方法 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //這些數(shù)據(jù)正常應(yīng)該中數(shù)據(jù)庫中讀 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3") .and() .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1"); }
測試狡耻,發(fā)現(xiàn)墩剖,登錄成功,并且每個角色只能訪問自己認證下的規(guī)則夷狰!搞定
權(quán)限控制和注銷
-
開啟自動配置的注銷的功能
//定制請求的授權(quán)規(guī)則 @Override protected void configure(HttpSecurity http) throws Exception { //.... //開啟自動配置的注銷的功能 // /logout 注銷請求 http.logout(); }
-
我們在前端岭皂,增加一個注銷的按鈕,
index.html
導(dǎo)航欄中<!--注銷--> <a class="item" th:href="@{/logout}"> <i class="sign-out icon"></i> 注銷 </a>
-
我們可以去測試一下孵淘,登錄成功后點擊注銷蒲障,發(fā)現(xiàn)注銷完畢會跳轉(zhuǎn)到登錄頁面!
-
但是瘫证,我們想讓他注銷成功后揉阎,依舊可以跳轉(zhuǎn)到首頁,該怎么處理呢背捌?
源碼:
// .logoutSuccessUrl("/"); 注銷成功來到首頁 http.logout().logoutSuccessUrl("/");
測試毙籽,注銷完畢后,發(fā)現(xiàn)跳轉(zhuǎn)到首頁OK
-
我們現(xiàn)在又來一個需求:用戶沒有登錄的時候毡庆,導(dǎo)航欄上只顯示登錄按鈕坑赡,用戶登錄之后,導(dǎo)航欄可以顯示登錄的用戶信息及注銷按鈕么抗!還有就是毅否,比如kuangshen這個用戶,它只有 vip2蝇刀,vip3功能螟加,那么登錄則只顯示這兩個功能,而vip1的功能菜單不顯示吞琐!這個就是真實的網(wǎng)站情況了捆探!該如何做呢?
<font color=red>我們需要結(jié)合thymeleaf中的一些功能</font>
sec:authorize="isAuthenticated()"
:是否認證登錄站粟!來顯示不同的頁面Maven依賴:
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
- 整合包4(springsecurity4)——springboot版本2.0.9
- 整合包5(springsecurity5)——springboot版本之后
-
修改我們的前端頁面
導(dǎo)入命名空間
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
修改導(dǎo)航欄黍图,增加認證判斷
<!--登錄注銷--> <div class="right menu"> <!--如果未登錄--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/login}"> <i class="address card icon"></i> 登錄 </a> </div> <!--如果已登錄--> <div sec:authorize="isAuthenticated()"> <a class="item"> <i class="address card icon"></i> 用戶名:<span sec:authentication="principal.username"></span> 角色:<span sec:authentication="principal.authorities"></span> </a> </div> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <i class="sign-out icon"></i> 注銷 </a> </div> </div>
-
重啟測試,我們可以登錄試試看奴烙,登錄成功后確實助被,顯示了我們想要的頁面;
-
未登錄
-
登錄
-
-
點擊注銷產(chǎn)生的問題
-
整合包4(springsecurity4)
-
整合包5(springsecurity5)(不算問題缸沃,需要點擊確定恰起,才能回到首頁)
<font color=red>解決問題:</font>
- 它默認防止csrf跨站請求偽造,因為會產(chǎn)生安全問題
- 將請求改為post表單提交
- 在spring security中關(guān)閉csrf功能
http.csrf().disable();
再次點擊注銷按鈕之后(直接退出到首頁)
-
-
我們繼續(xù)將下面的角色功能塊認證完成趾牧!
<!--菜單根據(jù)用戶的角色動態(tài)的實現(xiàn)--> <div class="column" sec:authorize="hasRole('vip1')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 1</h5> <hr> <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div> <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div> <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div> </div> </div> </div> </div> <div class="column" sec:authorize="hasRole('vip2')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 2</h5> <hr> <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div> <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div> <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div> </div> </div> </div> </div> <div class="column" sec:authorize="hasRole('vip3')"> <div class="ui raised segment"> <div class="ui"> <div class="content"> <h5 class="content">Level 3</h5> <hr> <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div> <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div> <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div> </div> </div> </div> </div>
- 測試一下检盼!
-
用戶首頁未登錄
-
某個用戶登錄
權(quán)限控制和注銷搞定!
記住我
現(xiàn)在的情況翘单,我們只要登錄之后吨枉,關(guān)閉瀏覽器貌亭,再登錄柬唯,就會讓我們重新登錄,但是很多網(wǎng)站的情況圃庭,就是有一個記住密碼的功能锄奢,這個該如何實現(xiàn)呢?很簡單
-
開啟記住我功能
//定制請求的授權(quán)規(guī)則 @Override protected void configure(HttpSecurity http) throws Exception { //剧腻。拘央。。书在。灰伟。。儒旬。栏账。。栈源。挡爵。 //開啟記住我功能: cookie,默認保存兩周 http.rememberMe(); }
-
我們再次啟動項目測試一下
-
發(fā)現(xiàn)登錄頁多了一個記住我功能
-
我們登錄之后關(guān)閉 瀏覽器,然后重新打開瀏覽器訪問甚垦,發(fā)現(xiàn)用戶依舊存在了讨!
<font color=red>思考:如何實現(xiàn)的呢?其實非常簡單</font>
我們可以查看瀏覽器的cookie
-
-
我們點擊注銷的時候制轰,可以發(fā)現(xiàn),spring security 幫我們自動刪除了這個 cookie
cookie發(fā)送給瀏覽器保存胞谭,以后登錄帶上這個cookie垃杖,只要通過檢查就可以免登錄了。如果點擊注銷丈屹,則會刪除這個cookie调俘,具體的原理我們在JavaWeb階段都講過了,這里就不在多說了旺垒!
定制登錄頁
現(xiàn)在這個登錄頁面都是spring security 默認的彩库,怎么樣可以使用我們自己寫的Login界面呢?
-
在剛才的登錄頁配置后面指定 loginpage
protected void configure(HttpSecurity http) throws Exception { //...... // 沒有權(quán)限默認會到登錄頁面,需要開啟登錄的頁面 // /login頁面 http.formLogin().loginPage("/toLogin"); //...... }
-
然后前端也需要指向我們自己定義的 login請求
<div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登錄 </a> </div>
-
我們登錄先蒋,需要將這些信息發(fā)送到哪里骇钦,我們也需要配置,login.html 配置提交請求及方式竞漾,方式必須為post:
在 loginPage()源碼中的注釋上有寫明:
-
這個請求提交上來眯搭,我們還需要驗證處理窥翩,怎么做呢?我們可以查看formLogin()方法的源碼鳞仙!我們配置接收登錄的用戶名和密碼的參數(shù)寇蚊!
protected void configure(HttpSecurity http) throws Exception { //...... // 沒有權(quán)限默認會到登錄頁面,需要開啟登錄的頁面 // /login頁面 http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陸表單提交請求 //...... }
-
在登錄頁增加記住我的多選框
<input type="checkbox" name="remember"> 記住我
-
后端驗證處理!
protected void configure(HttpSecurity http) throws Exception { //...... //開啟記住我功能: cookie,默認保存兩周,自定義接收前端的參數(shù) http.rememberMe().rememberMeParameter("remember"); }
測試棍好,OK
完整配置代碼
package nuc.ss.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// AOP:攔截器
@EnableWebSecurity // 開啟WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//鏈?zhǔn)骄幊? //授權(quán)
@Override
protected void configure(HttpSecurity http) throws Exception {
// 首頁所有人都可以訪問仗岸,功能也只有對應(yīng)有權(quán)限的人才能訪問到
// 請求授權(quán)的規(guī)則
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// 沒有權(quán)限默認會到登錄頁面,需要開啟登錄的頁面
// /login頁面
http.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/toLogin")
.loginProcessingUrl("/login");
//注銷,開啟了注銷功能,跳到首頁
http.logout().logoutSuccessUrl("/");
// 防止網(wǎng)站工具:get,post
http.csrf().disable();//關(guān)閉csrf功能借笙,登錄失敗肯定存在的原因
//開啟記住我功能: cookie,默認保存兩周,自定義接收前端的參數(shù)
http.rememberMe().rememberMeParameter("remember");
}
// 認證扒怖,springboot 2.1.x 可以直接使用
// 密碼編碼: PasswordEncoder
// 在spring Secutiry 5.0+ 新增了很多加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//這些數(shù)據(jù)正常應(yīng)該中數(shù)據(jù)庫中讀
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}