系列文章
SpringBoot 01 —— HelloSpringBoot、yaml配置、資料校驗、多環境切換
SpringBoot 02 —— Web簡單探究、員工管理系統
SpringBoot 03 —— Spring Security
SpringBoot 04 —— Shiro
SpringBoot 05 —— Swagger
SpringBoot 06 —— 異步任務、郵件任務、定時任務
SpringBoot 07 —— 分布式:Dubbo+Zookeeper
文章目錄
- 系列文章
- 十一、Spring Security
-
- 11.1、實驗環境搭建
- 11.2、認證和授權
- 11.3、登出和權限控制
- 11.4、記住我和定制登入頁
十一、Spring Security
簡介
Spring Security是一個功能強大且可高度定制的關于身份驗證和通路控制的架構。 它是用于保護基于Spring的應用程式的标準。
SpringSecurity是來解決Web開發的安全性問題,就像之前我們寫過的過濾器和攔截器,而SpringSecurity就相當于過濾器的一個架構,能幫我們省略很多代碼。
比如之前我們用攔截器或者過濾器來進行通路控制,要求使用者必須登入才能進入首頁面,或者對于權限不同的使用者進入的頁面也不同等,但之前寫的代碼都較為複雜、備援,而用架構就能簡單實作。
需要記住幾個關鍵類:
- WebSecurityConfigurerAdapter:自定義安全政策
- AuthenticationManagerBuilder:自定義認證政策
- @EnableWebSecurity:打開WebSecurity功能(看見@EnableXXX 就是開啟某個功能)
Spring Security的兩個主要目标就是"認證"和“授權”(也就是通路控制)。後面學習的Shiro也是一樣的。
官方文檔:https://spring.io/guides/gs/securing-web/
下面我們搭建一個正常的SpringBoot Web項目。
11.1、實驗環境搭建
1、建立SpringBoot項目,添加Web子產品,并導入Thymeleaf包。
<!--thymeleaf—— 命名空間xmlns:th="http://www.thymeleaf.org“ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、導入靜态資源檔案(從網上找的,可以不用這一步,隻是為了讓界面好看點,現在的操作都沒涉及到security)
資源下載下傳連結
連結: https://pan.baidu.com/s/1UoUJg6CsHm5bQbouYt609A 提取碼: nv8p
CSDN:https://download.csdn.net/download/qq_39763246/15982736
需要将Thymeleaf的緩存關閉
3、編寫一個路由轉發的Controller,将各個頁面轉發。
package com.zcy.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
//使用者路由轉發的Controller
public class RouterController {
//輸入 localhost:8080或者localhost:8080/index都會跳轉到首頁
@RequestMapping({"/index", "/"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
//通過Restful風格,在URL輸入 level1/1就會跳轉到1.html,輸入level2/2就跳轉到2.html
@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;
}
}
4、效果
11.2、認證和授權
建立一個SecurityConfig.java
package com.zcy.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,橫切的思想,不會去改變我們Controller的代碼。類似于攔截器,但更強大!
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授權
@Override
protected void configure(HttpSecurity http) throws Exception {
//鍊式程式設計
//實作的功能:首頁index 所有人都能通路,具體的功能也level 隻能被有相應權限的人通路
//給請求添加規則,使的通路level1下的所有請求都要有vip1權限
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//如果沒有權限,預設跳轉至登入頁面。這是Spring Security裡内置的
http.formLogin();
}
//認證(Spring Security 5.0+ 要求對密碼加密才能正常使用)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//從記憶體中讀取使用者(提前設定好的使用者,真實開發還是從資料庫中讀取,後面Shiro再講)
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zcy1").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
.and()
.withUser("zcy2").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2")
.and()
.withUser("zcy3").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3");
//從資料庫讀使用者
}
}
效果:
1、未登入時通路各個頁面都會跳轉到登入頁面。可以發現URL欄的請求是 /login,但我們并沒有寫這個頁面,我們的login是views/login。這個頁面是Spring Security的
http.formLogin();
中提供的。
http.formLogin();
的源碼注解:
2、登入使用者zcy1,隻有vip1的權限,隻能通路level1下的頁面,通路其他頁面會被攔截。
3、登入使用者zcy3,擁有vip1、vip2、vip3,可以通路所有頁面。
11.3、登出和權限控制
效果圖:
- 未登入時
- 登入zcy1(注意,此時我們沒有定制登入頁面,用的是security内置的,是以點選登入按鈕無效,隻能URL輸入/login)
- 登出zcy1,就傳回到首頁
- 登入zcy2
實作方法:
1、在index.html中添加登出
<!--登入登出-->
<div class="right menu">
<!--未登入-->
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登入
</a>
<!-- 登出 -->
<a class="item" th:href="@{/logout}">
<i class="sign-out card icon"></i> 登出
</a>
</div>
2、在SecurityConfig.java中添加登出
//授權
@Override
protected void configure(HttpSecurity http) throws Exception {
//鍊式程式設計
//實作的功能:首頁index 所有人都能通路,具體的功能也level 隻能被有相應權限的人通路
//給請求添加規則,使的通路level1下的所有請求都要有vip1權限
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/views/level1/**").hasRole("vip1")
.antMatchers("/views/level2/**").hasRole("vip2")
.antMatchers("/views/level3/**").hasRole("vip3");
//如果沒有權限,預設跳轉至一個内置登入頁面/login。這是Spring Security裡内置的。
http.formLogin(); //.loginPage("可以在這裡指定自己的登入頁面);
//關閉網站防攻擊,預設會開啟,因為我們前端用的<a>,是用get方式送出,不安全。
http.csrf().disable();//如果沒有這句,登出會404
//開啟登出功能,logoutSuccessUrl作用是登出成功跳轉到新url,點選登出跳轉到首頁
http.logout().logoutSuccessUrl("/");
}
http.logout()
源碼注釋:
3、Thymeleaf整合Spring Security
我們原來定制化首頁是利用後端傳值給前端,前端進行判斷,然後顯示,例如
<div th:if="${session.user.name}">
...顯示專屬内容
</div>
現在我們在Thymeleaf中直接整合Spring Security
- 先導包
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
-
降低Spring Boot版本至2.0.7(Thymeleaf不支援高版本去整合Security,現在一般不會去整合Security)
降低後,會下載下傳新版本,2.0.7的版本,我們還需要改變junit的包,詳情看下面圖檔。
- 修改index.html
11.4、記住我和定制登入頁
1、記住我:不登出,重新進入該頁面會自動登入
SecurityConfig.java的configure方法添加代碼:
//記住我,原理:添加Cookie到浏覽器,關閉浏覽器重新進入頁面後保持登入狀态
http.rememberMe();//預設儲存兩周
效果:
2、定制登入頁
修改SecurityConfig.java的configure方法:
//如果沒有權限,預設跳轉至一個内置登入頁面/login。這是Spring Security裡内置的。
//usernameParameter和passwordParameter指定前端傳遞過來的參數名稱
//loginPage指定自己的登入頁面
http.formLogin()
.loginPage("/toLogin")
.usernameParameter("user")
.passwordParameter("pwd");
//.loginProcessingUrl("/login")可指定真正的URL,即在浏覽器輸入/toLogin,但請求是/login
//記住我,原理:添加Cookie到浏覽器,關閉浏覽器重新進入頁面後保持登入狀态
//預設儲存兩周.rememberMeParameter指定前端傳遞的參數名稱
http.rememberMe()
.rememberMeParameter("remember");
修改index.html,原來參數名是username和password,現在修改了。并新增一個單選框,記住我。
效果:首頁點選登入按鈕可以正常登入了,且具備記住我功能。