1. 程式人生 > >SpringBoot第二十三篇:安全性之Spring Security

SpringBoot第二十三篇:安全性之Spring Security

作者:追夢1819
原文:https://www.cnblogs.com/yanfei1819/p/11350255.html
版權宣告:本文為博主原創文章,轉載請附上博文連結!

引言

  系統的安全的重要性人人皆知,其也成為評判系統的重要標準。

  Spring Security 是基於 Spring 的安全框架。傳統的 Spring Security 框架需要配置大量的 xml 檔案。而 SpringBoot 的出現,使其簡單、方便、上手快。


版本資訊

  • JDK:1.8
  • SpringBoot :2.1.6.RELEASE
  • maven:3.3.9
  • Thymelaf:2.1.4.RELEASE
  • IDEA:2019.1.1


資料庫設計

  系統的底層資料庫,設計的表格是五張:使用者表、角色表、使用者角色對應表、許可權表、角色許可權對應表。使用者與角色對應,角色與許可權對應,從而使使用者與許可權間接對應。同時考慮到了擴充套件性和健壯性。這就是底層設計的核心思想。

  上述的底層設計基本上是千篇一律的,沒啥可以講的。不是本文的重點。本文的重點是通過專案的需求來演示完整的功能實現。

搭建環境

  為了便於專案的演示,本章的例項用 SpringBoot + thymelaf 構建一個簡單的頁面。同時,由於功能點比較多,並保證能夠同時講解晚上功能,以下將分階段詳解各個功能點。

第一階段:

第一步,建立專案:

對以上的專案目錄說明:

com.yanfei1819.security.config.SecurityConfig:security配置

com.yanfei1819.security.web.controller.IndexController:測試介面

com.yanfei1819.security.SecurityApplication:啟動類

src\main\resources\templates\index.html:首頁

src\main\resources\templates\springboot-1.html:同以下三個頁面都是選單的詳細頁,用來模擬選單

src\main\resources\templates\springboot-2.html:

src\main\resources\templates\work-1.html:

src\main\resources\templates\work-2.html:

src\main\resources\application.properties:主配置檔案

第二步,引入 maven 依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

注意,在引入 security 依賴後,如果沒有做配置,它會將所有的請求攔截,並跳轉到自定義的登入介面(埠號被定義為8085)。如下圖:

第三步,建立配置類 SecurityConfig ,並繼承 WebSecurityConfigurerAdapter:

package com.yanfei1819.security.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;

/**
 * Created by 追夢1819 on 2019-06-27.
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 定製授權規則
        http.authorizeRequests().antMatchers("/").permitAll(). // 所有角色可訪問
                antMatchers("/springboot/**").hasAnyRole("admin","test"). // 只有xx角色才能訪問
                antMatchers("/work/**").hasRole("admin"); // 只有xx角色才能訪問
    }
}

定義授權規則,需要重寫 configure(HttpSecurity http) 方法。該配置類的寫法,可以參照 Spring Security官網。該方法中是定製授權規則。

hasAuthority([auth]):等同於hasRole
hasAnyAuthority([auth1,auth2]):等同於hasAnyRole
hasRole([role]):當前使用者是否擁有指定角色。
hasAnyRole([role1,role2]):多個角色是一個以逗號進行分隔的字串。如果當前使用者擁有指定角色中的任意一個則返回true
Principle:代表當前使用者的principle物件
authentication:直接從SecurityContext獲取的當前Authentication物件
permitAll():總是返回true,表示允許所有的
denyAll():總是返回false,表示拒絕所有的
isAnonymous():當前使用者是否是一個匿名使用者
isAuthenticated():表示當前使用者是否已經登入認證成功了
isRememberMe():表示當前使用者是否是通過Remember-Me自動登入的
isFullyAuthenticated():如果當前使用者既不是一個匿名使用者,同時又不是通過Remember-Me自動登入的,則返回true
hasPermission():當前使用者是否擁有指定許可權

第四步,定義介面:

package com.yanfei1819.security.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * Created by 追夢1819 on 2019-06-27.
 */
@Controller
public class IndexController {
    @GetMapping("/")
    public String index(){
        return "index";
    }
    @GetMapping("/springboot/{id}")
    public String springbootById(@PathVariable int id){
        return "springboot-"+id;
    }
    @GetMapping("/work/{id}")
    public String work(@PathVariable int id){
        return "work-"+id;
    }
}

第五步,編寫頁面 index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>我是首頁</h1>
<di>
    <h3>追夢1819的部落格系列</h3>
    <ul>
        <li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
        <li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
        <li><a th:href="@{/work/1}">work 第一章</a></li>
        <li><a th:href="@{/work/2}">work 第二章</a></li>
    </ul>
</di>
</body>
</html>

SpringBoot-1.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>SpringBoot-1</h1>

</body>
</html>

另外的 springboot-2.html、work-1.html、work-2.html 與以上類似,此不再贅述。

第六步,啟動類是:

package com.yanfei1819.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
}

最後,啟動專案。直接訪問 http://localhost:8085/ ,進入首頁:

點選其中任意一個連結:

可以看到是沒有許可權訪問的。因此,上述的 security 配置成功。


第二階段:

  開啟自動配置的登入功能,也就是在 SecurityConfig 配置類中加入以下程式碼:

        http.formLogin();

該功能的作用是,進入首頁後,點選選單,如果沒有許可權,則跳轉到登入頁。


第三階段:

下面闡述設定登入賬號和密碼。

在 SecurityConfig 配置類重寫 configure(AuthenticationManagerBuilder auth) 方法:

    // 定義認證規則
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("admin", "test")
                .and().withUser("test").password("123456").roles("test");
    }

注意,此處會有一個問題。如以上地址認證規則,在使用配置的賬號登入時會報錯:

這是由於在 Spring Security5.0 版本後,新增了加密方式,改變了密碼的格式。

在官網中有描述:

The general format for a password is:


Such that `id` is an identifier used to look up which `PasswordEncoder` should be used and `encodedPassword` is the original encoded password for the selected `PasswordEncoder`. The `id` must be at the beginning of the password, start with `{` and end with `}`. If the `id` cannot be found, the `id` will be null. For example, the following might be a list of passwords encoded using different `id`. All of the original passwords are "password".

{bcrypt}$2a\(10\)dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 1
{noop}password 2
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 3
{scrypt}$e0801\(8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==\)OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= 4
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 5
```

1 The first password would have a PasswordEncoder id of bcrypt and encodedPassword of $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG. When matching it would delegate to BCryptPasswordEncoder
2 The second password would have a PasswordEncoder id of noop and encodedPassword of password. When matching it would delegate to NoOpPasswordEncoder
3 The third password would have a PasswordEncoder id of pbkdf2 and encodedPassword of 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc. When matching it would delegate to Pbkdf2PasswordEncoder
4 The fourth password would have a PasswordEncoder id of scrypt and encodedPassword of $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=When matching it would delegate to SCryptPasswordEncoder
5 The final password would have a PasswordEncoder id of sha256 and encodedPassword of 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0. When matching it would delegate to StandardPasswordEncoder

上面這段話的解釋了為什麼會報錯:There is no PasswordEncoder mapped for the id "null",同時給出瞭解決方案。也就是 configure(AuthenticationManagerBuilder auth) 方法修改為:

    // 定義認證規則
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("admin","test")
                .and().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("test").password(new BCryptPasswordEncoder().encode("123456")).roles("test");
    }

修改後重啟專案,登入可正常訪問:

訪問結果是:賬號 admin/123456 可以訪問所有選單:SpringBoot 第一章、SpringBoot 第二章、work 第一章、work 第二章,賬號 test/123456 只能訪問 SpringBoot 第一章、SpringBoot 第二章。


第四階段:

  開啟自動配置的登出功能,並清除 session,在配置類 SecurityConfig 中的 configure(HttpSecurity http) 方法中新增:

http.logout();

然後在首頁 index.html 中新增一個登出按鈕:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>我是首頁</h1>
<di>
    <h3>追夢1819的部落格系列</h3>
    <ul>
        <li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
        <li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
        <li><a th:href="@{/work/1}">work 第一章</a></li>
        <li><a th:href="@{/work/2}">work 第二章</a></li>
    </ul>
</di>
<div>
    <form method="post" th:action="@{/logout}"> 
        <input type="submit" value="logout">
    </form>
</div>
</body>
</html>

啟動專案,進入首頁,點選 【logout】,會跳轉到登入介面,同時連結中帶了引數 ?logout

當然,也可以跳轉到定製的頁面,只要將屬性修改為:

        http.logout()  // 退出並清除session
                .logoutSuccessUrl("/");


第五階段:

  以上的功能基本都滿足了我們專案中的需求。不過只講述了功能點。下面我們將闡述如何在頁面展示以上功能。

  首先,我們必須引入以下依賴,以便使用 sec:authentication和sec:authorize 屬性。

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

注意: 此處有版本衝突問題,以上的演示的 SpringBoot 用的版本都是 2.1.6.RELEASE。但是在此如果繼續使用該版本,則無法使用以上依賴中的 sec:authentication和sec:authorize 屬性。作者在做此演示時,對 SpringBoot 版本作了降級處理,版本為 2.1.4.RELEASE。而舊的版本有很多不同的地方,例如舊版本的登入介面是:

此處需要特別注意!


引入上述依賴後,我們將首頁進行改造:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>我是首頁</h1>
<!--沒有登入-->
<div sec:authorize="!isAuthenticated()">
    <a th:href="@{/login}">login</a>
</div>
<!--已登入-->
<div sec:authorize="isAuthenticated()">
    <div>
        <form method="post" th:action="@{/logout}">
            <input type="submit" value="logout">
        </form>
    </div>
    登陸者:<span sec:authentication="name"></span>
    登陸者角色:<span sec:authentication="principal.authorities"></span>
</div>
<div>
    <h3>追夢1819的部落格系列</h3>
    <ul>
        <!-- 通過角色判斷是否展示-->
        <div sec:authorize="hasRole('admin')">
            <li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
            <li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
        </div>
        <div sec:authorize="hasRole('test')">
            <li><a th:href="@{/work/1}">work 第一章</a></li>
            <li><a th:href="@{/work/2}">work 第二章</a></li>
        </div>
    </ul>
</div>
</body>
</html>

啟動專案,分別用不登入、 admin/123456、test/123456 登入,檢視效果:


第六階段:

  最後我們講解一個常用的功能,就是登陸的記住功能,配置很簡單,在配置類 SecurityConfig 中的 configure(HttpSecurity http) 方法中新增即可:

        http.rememberMe() // 記住功能
                .rememberMeParameter("remember") //自定義rememberMe的name值,預設remember-Me
                .tokenValiditySeconds(10); // 記住時間

進入登陸介面:

新增該方法後,登入頁會出現記住功能的複選框。


總結

  還有很多詳細的功能。由於篇幅所限,本章中不做一一細解。如果想了解更多,作者給讀者的建議是,可以多看看 WebSecurityConfigurerAdapterHttpSecurityAuthenticationManagerBuilder 等類的原始碼,比較簡單,很容易上手。另外就是其文件非常的詳細、清晰(文件詳細是Spring的一個特色)。可以讓大家先感受一下 Spring 原始碼文件的強大:

功能描述、示例一應俱全。


結語

  其實對以上功能的瞭解,不算很難。但是這篇部落格前後寫了六七個小時。作者看了翻閱了不少的資料,通讀對應的官方文件,聽了一些比較好的課程,然後自己一一校驗,思考,排版,解決版本衝突等。最終是希望讓讀者能夠看到一篇準確、美觀、較詳細的資料,不至於陷入網上的亂七八糟的資料中無法自拔。


參考

  1. Spring Security Reference
  2. Hello Spring Security with Boot
  3. WebSecurityConfigurerAdapterHttpSecurityAuthenticationManagerBuilder 等類的原始碼



相關推薦

SpringBoot第二十三安全性Spring Security

作者:追夢1819 原文:https://www.cnblogs.com/yanfei1819/p/11350255.html 版權宣告:本文為博主原創文章,轉載請附上博文連結! 引言   系統的安全的重要性人人皆知,其也成為評判系統的重要標準。   Spring Security 是基於 Spring 的

一起來學SpringBoot | 第二十三輕鬆搞定重複提交(分散式鎖)

SpringBoot 是為了簡化 Spring 應用的建立、執行、除錯、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規範,引入相關的依賴就可以輕易的搭建出一個 WEB 工程 在 一起來學S

轉載SpringBoot非官方教程 | 第二十三 非同步方法

轉載:https://blog.csdn.net/forezp/article/details/71024169 這篇文章主要介紹在springboot 使用非同步方法,去請求github api. 建立工程 在pom檔案引入相關依賴: <dependency

一起來學SpringBoot | 第十三RabbitMQ延遲佇列

SpringBoot 是為了簡化 Spring 應用的建立、執行、除錯、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規範,引入相關的依賴就可以輕易的搭建出一個 WEB 工程 初探Rabbi

SpringBoot十三日誌處理

作者:追夢1819 原文:https://www.cnblogs.com/yanfei1819/p/10973583.html 版權宣告:本文為博主原創文章,轉載請附上博文連結! 引言   日誌是軟體系統的“基礎設施”,它可以幫助我們瞭解系統的執行軌跡,查詢系統的執行異常等。很多人都沒有引起對日誌的重視。

十三Spring Boot郵件服務

傳送郵件應該是網站的必備功能之一,什麼註冊驗證,忘記密碼或者是給使用者傳送營銷資訊。最早期的時候我們會使用JavaMail的相關API來編寫傳送郵件的相關程式碼,後來Spring推出了JavaMailSender更加簡化了郵件傳送的過程,再之後Spring Boot對此進行了封裝就有了現

SpringBoot非官方教程 | 第十三springboot整合spring cache

本文介紹如何在springboot中使用預設的spring cache, 宣告式快取 Spring 定義 CacheManager 和 Cache 介面用來統一不同的快取技術。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redi

“全棧2019”Java第二十三流程控制語句中決策語句switch上

難度 初級 學習時間 10分鐘 適合人群 零基礎 開發語言 Java 開發環境 JDK v11 IntelliJ IDEA v2018.3 文章原文連結 “全棧2019”Java第二十三章:流程控制語句中決策語句switch上篇 下一章 “全棧2019”Java第二十四

第二SpringCloud服務消費(Ribbon)

在微服務架構中,業務都會被拆分成一個獨立的服務,服務與服務的通訊是基於Http RESTful的。SpringCloud有兩種服務呼叫方式,一種是Ribbon+RESTTemplate,另一種是Feign。在這一篇文章首先講解下基於Ribbon+REST。 Ribbon簡介

敏捷結果30天第二十三執行中的專注

 時間管理的最終目的是高效的把事情做成並且要做好。有目標,有計劃,按照計劃去執行。執行的過程會遇到就會遇到拖延,我們就需要在執行過程中專注,只要專注後執行力和效率才能提高。   對於個人管理而言:   1.要有階段型目標,並且要制訂一個執行計劃   2.有執行計劃

Python路【第二十三】爬蟲

difference between urllib and urllib2 自己翻譯的裝逼必備  What is the difference between urllib and urllib2 modules of Python? #python的urllib2

一起來學SpringBoot | 第十五actuator與spring-boot-admin 可以說的祕密

SpringBoot 是為了簡化 Spring 應用的建立、執行、除錯、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規範,引入相關的依賴就可以輕易的搭建出一個 WEB 工程 一起來學Spr

第二十三SpringBoot專案多模組打包與部署

我們接上章節內容繼續,上一章我們已經完成了多模組專案的建立以及執行,那我們線上環境該如何打包部署呢? 本章目標 打包部署SpringBoot多模組專案到外部執行Tomcat容器中。 構建專案 我們直接複製一份上一章的程式碼,如下圖1所示:

SpringBoot第九整合Spring Data JPA

作者:追夢1819 原文:https://www.cnblogs.com/yanfei1819/p/10910059.html 版權宣告:本文為博主原創文章,轉載請附上博文連結! 前言   前面幾章,我們介紹了 JDBCTemplate、MyBatis 等 ORM 框架。下面我們來介紹極簡模式的 Sprin

跟我學SpringCloud | 第十三Spring Cloud Gateway服務化和過濾器

SpringCloud系列教程 | 第十三篇:Spring Cloud Gateway服務化和過濾器 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 上一篇文章服務閘道器 Spring Cloud G

第二十三 jQuery 學習5 添加元素

展示 頁面 script 興趣 提示 什麽 urn round wid jQuery 學習5 添加元素 同學們,這節課,使用jQuery動態添加元素,是很關鍵的一課,一般用在什麽地方呢:別人發表一篇文章,你評論樓主的時候,總不能是提交表單,到後臺的其他頁面處理,然後再刷

第十五JavaScript Dom操作

頁面 對象模型 面向 方法 bsp log gpo eva div 一、後臺管理頁面布局 二、JavaScript函數 三、eval以及時間操作 四、JavaScript作用域 五、JavaScript面向對象模型 六、DOM選擇器 七、DOM事件操作 八、DOM綁定事件的

python全棧開發基礎【第二十三】線程

不同 控制 上海 strong 執行 不能 子進程 申請 什麽 一、什麽是線程 線程:顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個進程 所以,進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上

Django 【第二十三】優化查詢

所有 一次 inf app related 方式 del filter model 一、假設有三張表 Room id 1 2 .. 1000 User: id 1 .. 10000

第二十三 logging模塊(******)

closed out 原因 fin bubuko struct ace 更多 mon 日誌非常重要,而且非常常用,可以通過logging模塊實現。 熱身運動 import logging logging.debug("debug message") logging.i