集成SpringSecurity

原文鏈接:公衆號狂神說

SpringSecurity

安全簡介

在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足用戶的要求,並可能造成用戶的隱私數據被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的發佈進程。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。

市面上存在比較有名的:Shiro,Spring Security !

這裏需要闡述一下的是,每一個框架的出現都是爲了解決某一問題而產生了,那麼Spring Security框架的出現是爲了解決什麼問題呢?

首先我們看下它的官網介紹:Spring Security官網地址

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

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架。它實際上是保護基於spring的應用程序的標準。

Spring Security是一個框架,側重於爲Java應用程序提供身份驗證和授權。與所有Spring項目一樣,Spring安全性的真正強大之處在於它可以輕鬆地擴展以滿足定製需求

從官網的介紹中可以知道這是一個權限框架。想我們之前做項目是沒有使用框架是怎麼控制權限的?對於權限 一般會細分爲功能權限,訪問權限,和菜單權限。代碼會寫的非常的繁瑣,冗餘。

怎麼解決之前寫權限代碼繁瑣,冗餘的問題,一些主流框架就應運而生而Spring Scecurity就是其中的一種。

Spring 是一個非常流行和成功的 Java 應用開發框架。Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案。一般來說,Web 應用的安全性包括用戶認證(Authentication)和用戶授權(Authorization)兩個部分。用戶認證指的是驗證某個用戶是否爲系統中的合法主體,也就是說用戶能否訪問該系統。用戶認證一般要求用戶提供用戶名和密碼。系統通過校驗用戶名和密碼來完成認證過程。用戶授權指的是驗證某個用戶是否有權限執行某個操作。在一個系統中,不同用戶所具有的權限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以進行修改。一般來說,系統會爲不同的用戶分配不同的角色,而每個角色則對應一系列的權限。

對於上面提到的兩種應用情景,Spring Security 框架都有很好的支持。在用戶認證方面,Spring Security 框架支持主流的認證方式,包括 HTTP 基本認證、HTTP 表單驗證、HTTP 摘要認證、OpenID 和 LDAP 等。在用戶授權方面,Spring Security 提供了基於角色的訪問控制和訪問控制列表(Access Control List,ACL),可以對應用中的領域對象進行細粒度的控制。

實戰測試

實驗環境搭建

1、新建一個初始的springboot項目web模塊,thymeleaf模塊

2、導入靜態資源

 

3、controller跳轉!

package com.jia.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("/lv1/{id}")
    public String lv1(@PathVariable int id){
        return "views/lv1/"+id;
    }

    @RequestMapping("/lv2/{id}")
    public String lv2(@PathVariable int id){
        return "views/lv2/"+id;
    }

    @RequestMapping("/lv3/{id}")
    public String lv3(@PathVariable int id){
        return "views/lv3/"+id;
    }
}

4、測試實驗環境是否OK!

認識SpringSecurity

Spring Security 是針對Spring項目的安全框架,也是Spring Boot底層安全模塊默認的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入 spring-boot-starter-security 模塊,進行少量的配置,即可實現強大的安全管理!

記住幾個類:

  • WebSecurityConfigurerAdapter:自定義Security策略

  • AuthenticationManagerBuilder:自定義認證策略

  • @EnableWebSecurity:開啓WebSecurity模式

Spring Security的兩個主要目標是 “認證” 和 “授權”(訪問控制)。

“認證”(Authentication)

身份驗證是關於驗證您的憑據,如用戶名/用戶ID和密碼,以驗證您的身份。

身份驗證通常通過用戶名和密碼完成,有時與身份驗證因素結合使用。

“授權” (Authorization)

授權發生在系統成功驗證您的身份後,最終會授予您訪問資源(如信息,文件,數據庫,資金,位置,幾乎任何內容)的完全權限。

這個概念是通用的,而不是隻在Spring Security 中存在。

 

認證和授權

目前,我們的測試環境,是誰都可以訪問的,我們使用 Spring Security 增加上認證和授權的功能

1、引入 Spring Security 模塊

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

2、編寫 Spring Security 配置類

參考官網:https://spring.io/projects/spring-security

查看我們自己項目中的版本,找到對應的幫助文檔:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5  

#servlet-applications 8.16.4

3、編寫基礎配置類

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 {
       
  }
}

4、定製請求的授權規則

    //授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首頁誰都可以訪問
        //功能項只能有對應權限的人才能訪問
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/lv1/**").hasRole("vip1")
                .antMatchers("/lv2/**").hasRole("vip2")
                .antMatchers("/lv3/**").hasRole("vip3");
    }

5、測試一下:發現除了首頁都進不去了!因爲我們目前沒有登錄的角色,因爲請求需要登錄的角色擁有對應的權限纔可以!

6、在configure()方法中加入以下配置,開啓自動配置的登錄功能!

// 開啓自動配置的登錄功能
// /login 請求來到登錄頁
// /login?error 重定向到這裏表示登錄失敗
http.formLogin();

7、測試一下:發現,沒有權限的時候,會跳轉到登錄的頁面!

8、查看剛纔登錄頁的註釋信息;

我們可以定義認證規則,重寫 configure(AuthenticationManagerBuilder auth) 方法

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //這些數據正常應該從數據庫中獲取
        auth.inMemoryAuthentication()
                .withUser("jia").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }

9、測試,我們可以使用這些賬號登錄進行測試!發現會報錯!

There is no PasswordEncoder mapped for the id “null”

10、原因,我們要將前端傳過來的密碼進行某種方式加密,否則就無法登錄,修改代碼

    //認證
    //報500錯誤   需要密碼編碼: PasswordEncoder
    //在spring Secutiry 5.0+ 新增了很多加密方法 如:BCryptPasswordEncoder
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //這些數據正常應該從數據庫中獲取
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("jia").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }

權限控制和註銷

1、開啓自動配置的註銷的功能

    //授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    // ...
    // 開啓自動配置的註銷的功能
    // /logout 註銷請求
        http.logout();
    }

2、我們在前端,增加一個註銷的按鈕,index.html 導航欄中

<a th:href="@{/logout}">註銷</a>

3、我們可以去測試一下,登錄成功後點擊註銷,發現註銷完畢會跳轉到登錄頁面!

4、但是,我們想讓他註銷成功後,依舊可以跳轉到首頁,該怎麼處理呢?

    //授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    // ...
    // 開啓自動配置的註銷的功能
    // /logout 註銷請求
    // 註銷 跳到首頁
        http.logout().logoutSuccessUrl("/");
    }

5、測試,註銷完畢後,發現跳轉到首頁OK

6、我們現在又來一個需求:用戶沒有登錄的時候,導航欄上只顯示登錄按鈕,用戶登錄之後,導航欄可以顯示登錄的用戶信息及註銷按鈕!還有就是,比如kuangshen這個用戶,它只有 vip2,vip3功能,那麼登錄則只顯示這兩個功能,而vip1的功能菜單不顯示!這個就是真實的網站情況了!該如何做呢?

我們需要結合thymeleaf中的一些功能

sec:authorize="isAuthenticated()" :是否認證登錄!來顯示不同的頁面

Maven依賴:

        <!-- thymeleaf-security 整合包 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>

7、修改我們的 前端頁面

導入命名空間

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

修改導航欄,增加認證判斷

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <!-- 如果未登錄 -->
    <!-- Security 的登錄鏈接: /toLogin (必須這麼寫)-->
    <div sec:authorize="!isAuthenticated()">
        <a th:href="@{/toLogin}">登錄</a>
    </div>

    <!-- 如果已登錄 顯示用戶名、註銷 -->
    <!-- Security 的退出鏈接: /logout (必須這麼寫)-->
    <div sec:authorize="isAuthenticated()">
        用戶名:<span sec:authentication="name"></span>
        <a th:href="@{/logout}">註銷</a>
    </div>
</body>
</html>

8、重啓測試,我們可以登錄試試看,登錄成功後確實,顯示了我們想要的頁面;

9、如果註銷404了,就是因爲它默認防止csrf跨站請求僞造,因爲會產生安全問題,我們可以將請求改爲post表單提交,或者在spring security中關閉csrf功能;我們試試:在 配置中增加 http.csrf().disable();

        // ...        
        // 註銷 跳到首頁
        http.logout().logoutSuccessUrl("/");
        // 關閉csrf功能
        http.csrf().disable();

10、我們繼續將下面的角色功能塊認證完成!(index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <!-- 如果未登錄 -->
    <!-- Security 的登錄鏈接: /toLogin (必須這麼寫)-->
    <div sec:authorize="!isAuthenticated()">
        <a th:href="@{/toLogin}">登錄</a>
    </div>

    <!-- 如果已登錄 顯示用戶名、註銷 -->
    <!-- Security 的退出鏈接: /logout (必須這麼寫)-->
    <div sec:authorize="isAuthenticated()">
        用戶名:<span sec:authentication="name"></span>
        <a th:href="@{/logout}">註銷</a>
    </div>

    <!-- 菜單根據用戶的角色動態實現 -->
    <div sec:authorize="hasRole('vip1')">
        lv1-<br>
        <a th:href="@{/lv1/1}">lv1-1</a><br>
        <a th:href="@{/lv1/2}">lv1-2</a><br>
        <a th:href="@{/lv1/3}">lv1-3</a><br>
    </div>

    <div sec:authorize="hasRole('vip2')">
        lv2-<br>
        <a th:href="@{/lv2/1}">lv2-1</a><br>
        <a th:href="@{/lv2/2}">lv2-2</a><br>
        <a th:href="@{/lv2/3}">lv2-3</a><br>
    </div>

    <div sec:authorize="hasRole('vip3')">
        lv3-<br>
        <a th:href="@{/lv3/1}">lv3-1</a><br>
        <a th:href="@{/lv3/2}">lv3-2</a><br>
        <a th:href="@{/lv3/3}">lv3-3</a><br>
    </div>
</body>
</html>

11、測試一下!

12、權限控制和註銷搞定!

記住我

現在的情況,我們只要登錄之後,關閉瀏覽器,再登錄,就會讓我們重新登錄,但是很多網站的情況,就是有一個記住密碼的功能,這個該如何實現呢?很簡單

1、開啓記住我功能

//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   //。。。。。。。。。。。
   //記住我
   http.rememberMe();
}

2、我們再次啓動項目測試一下,發現登錄頁多了一個記住我功能,我們登錄之後關閉 瀏覽器,然後重新打開瀏覽器訪問,發現用戶依舊存在!

思考:如何實現的呢?其實非常簡單

我們可以查看瀏覽器的cookie

3、我們點擊註銷的時候,可以發現,spring security 幫我們自動刪除了這個 cookie

4、結論:登錄成功後,將cookie發送給瀏覽器保存,以後登錄帶上這個cookie,只要通過檢查就可以免登錄了。如果點擊註銷,則會刪除這個cookie,具體的原理我們在JavaWeb階段都講過了,這裏就不在多說了!

 

定製登錄頁

現在這個登錄頁面都是spring security 默認的,怎麼樣可以使用我們自己寫的Login界面呢?

1、在剛纔的登錄頁配置後面指定 loginpage

http.formLogin().loginPage("/toLogin");

2、然後前端也需要指向我們自己定義的 login請求

 <a th:href="@{/toLogin}">登錄</a>

3、我們登錄,需要將這些信息發送到哪裏,我們也需要配置,login.html 配置提交請求及方式,方式必須爲post:(login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/loginMark}" method="post">
    username <input type="text" name="user">
    password <input type="password" name="pwd" >
    <input type="checkbox" name="rememberMe"> 記住我
    <input type="submit" value="提交">
</form>
</body>
</html>

4、這個請求提交上來,我們還需要驗證處理,怎麼做呢?我們可以查看formLogin()方法的源碼!我們配置接收登錄的用戶名和密碼的參數!

        //沒有權限會開啓登錄頁
        //定製登錄頁
        //自定義接收前端的參數
        http.formLogin()
            .loginPage("/toLogin")
            .loginProcessingUrl("/loginMark") // 登陸表單提交請求
            .usernameParameter("user")
            .passwordParameter("pwd");

5、在登錄頁增加記住我的多選框

<input type="checkbox" name="rememberMe"> 記住我

6、後端驗證處理!

        //開啓 "記住我" 功能 cookie 14天有效
        //自定義接收前端的參數
        http.rememberMe().rememberMeParameter("rememberMe");

7、測試,OK

完整配置代碼

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

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首頁誰都可以訪問
        //功能項只能有對應權限的人才能訪問
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/lv1/**").hasRole("vip1")
                .antMatchers("/lv2/**").hasRole("vip2")
                .antMatchers("/lv3/**").hasRole("vip3");
        //沒有權限會開啓登錄頁
        //定製登錄頁
        //自定義接收前端的參數
        http.formLogin().loginPage("/toLogin").loginProcessingUrl("/loginMark").usernameParameter("user").passwordParameter("pwd");

        //註銷 跳到首頁
        http.logout().logoutSuccessUrl("/");
        //關閉csrf功能
        http.csrf().disable();

        //開啓 "記住我" 功能 cookie 14天有效
        //自定義接收前端的參數
        http.rememberMe().rememberMeParameter("rememberMe");
    }

    //認證
    //報500錯誤   需要密碼編碼: PasswordEncoder
    //在spring Secutiry 5.0+ 新增了很多加密方法 如:BCryptPasswordEncoder
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //這些數據正常應該從數據庫中獲取
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("jia").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
}

 

 

 

B站地址:https://space.bilibili.com/95256449

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章