SpringBoot整合Spring Security

2020-12-17 計算機java編程

前言

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架。提供了完善的認證機制和方法級的授權功能。是一款非常優秀的權限管理框架。它的核心是一組過濾器鏈,不同的功能經由不同的過濾器。這篇文章就是想通過一個小案例將Spring Security整合到SpringBoot中去。要實現的功能就是在認證伺服器上登錄,然後獲取Token,再訪問資源伺服器中的資源。

基本概念

單點登錄什麼叫做單點登錄呢。就是在一個多應用系統中,只要在其中一個系統上登錄之後,不需要在其它系統上登錄也可以訪問其內容。舉個例子,京東那麼複雜的系統肯定不會是單體結構,必然是微服務架構,比如訂單功能是一個系統,交易是一個系統……那麼我在下訂單的時候登錄了,付錢難道還需要再登錄一次嗎,如果是這樣,用戶體驗也太差了吧。實現的流程就是我在下單的時候系統發現我沒登錄就讓我登錄,登錄完了之後系統返回給我一個Token,就類似於身份證的東西;然後我想去付錢的時候就把Token再傳到交易系統中,然後交易系統驗證一下Token就知道是誰了,就不需要再讓我登錄一次。

JWT上面提到的Token就是JWT(JSON Web Token),是一種用於通信雙方之間傳遞安全信息的簡潔的、URL安全的表述性聲明規範。一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與籤名。為了能夠直觀地看到JWT的結構,我畫了一張思維導圖:

最終生成的JWT令牌就是下面這樣,有三部分,用 . 分隔。

base64UrlEncode(JWT 頭)+"."+base64UrlEncode(載荷)+"."+HMACSHA256(base64UrlEncode(JWT 頭) + "." + base64UrlEncode(有效載荷),密鑰)eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

RSA從上面的例子中可以看出,JWT在加密解密的時候都用到了同一個密鑰 「 robod666 」,這將會帶來一個弊端,如果被黑客知道了密鑰的內容,那麼他就可以去偽造Token了。所以為了安全,我們可以使用非對稱加密算法RSA

RSA的基本原理有兩點:

私鑰加密,持有私鑰或公鑰才可以解密公鑰加密,持有私鑰才可解密認證伺服器用戶登錄功能

前期準備

介紹完了基本概念之後就可以開始整合了,受限於篇幅,只貼最核心的代碼,其它內容請小夥伴們去源碼中找,地址在文末。 首先需要準備好資料庫:

一共三張表,分別是用戶表,角色表,用戶-角色表。用戶是登錄用的,密碼其實就是加密過的字符串,內容是「 123」;角色是做權限控制時用的。

然後創建一個空的父工程SpringSecurityDemo,然後在父工程裡面創建一個Module作為認證服務,名叫authentication_server。添加必要的依賴。(

內容較佔篇幅,有需要的去源碼中獲取,源碼地址見文末

)。

項目的配置文件內容截取了核心的部分貼在下面:

最後的公私鑰的標籤是自定義的,並不是Spring提供的標籤,後面我們會在RSA的配置類中去加載這一部分內容。

為了方便起見,我們還可以準備幾個工具類:

JsonUtils:提供了json相關的一些操作;JwtUtils:生成token以及校驗token相關方法;RsaUtils:生成公鑰私鑰文件,以及從文件中讀取公鑰私鑰。我們可以將載荷單獨封裝成一個對象:

現在再去寫一個測試類,調用RsaUtils中的相應方法去生成公鑰和私鑰。那公鑰私鑰生成好了在使用的時候是怎麼獲取的呢?為了解決這個問題,我們需要創建一個RSA的配置類,

首先我們使用了@ConfigurationProperties註解去指定公鑰私鑰路徑的key,然後在構造方法中就可以去獲取到公鑰私鑰的內容了。這樣在需要公鑰私鑰的時候就可以直接調用這個類了。但是不放入Spring容器中怎麼調用這個類,所以在啟動類中添加一個註解:

@EnableConfigurationProperties(RsaKeyProperties.class)

這表示把RSA的配置類放入Spring容器中。

用戶登錄

在實現用戶登錄的功能之前,先說一下登錄的相關內容。關於登錄流程我在網上看了篇文章感覺挺好的,貼出來給小夥伴們看看:

首先會進入UsernamePasswordAuthenticationFilter並且設置權限為null和是否授權為false,然後進入ProviderManager查找支持UsernamepasswordAuthenticationTokenprovider並且調用provider.authenticate(authentication);再然後就是UserDetailsService接口的實現類(也就是自己真正具體的業務了),這時候都檢查過了後,就會回調UsernamePasswordAuthenticationFilter並且設置權限(具體業務所查出的權限)和設置授權為true(因為這時候確實所有關卡都檢查過了)。

在上面這段話中,提到了一個UsernamePasswordAuthenticationFilter,我們一開始進入的就是這個過濾器的attemptAuthentication()方法,但是這個方法是從form表單中獲取用戶名密碼,和我們的需求不符,所以我們需要重寫這個方法。然後經過一系列的周轉,進入到了UserDetailsService.loadUserByUsername()方法中,所以我們為了實現自己的業務邏輯,需要去實現這個方法。

這個方法返回的是一個UserDetails接口對象,如果想返回自定義的對象,可以去實現這個接口。最終用戶驗證成功之後,調用的是UsernamePasswordAuthenticationFilter的父類AbstractAuthenticationProcessingFilter.successfulAuthentication()方法,我們也需要去重寫這個方法去實現我們自己的需求。

所以現在就來實現一下上面說的這些東西吧

我們自定義了一個SysUser類去實現UserDetails接口,然後添加了幾個自定義的欄位

在這段代碼中,我們先定義了一個接口UserService去繼承UserDetailsService,然後用UserServiceImpl實現了UserService,就相當於UserServiceImpl實現了UserDetailsService,這樣我們就可以去實現loadUserByUsername()方法,內容很簡單,就是用用戶名去資料庫中查出對應的SysUser,然後具體的驗證流程就可以交給其它的過濾器去實現了,我們就不用管了。

前面提到了需要去重寫attemptAuthentication()successfulAuthentication()方法,那就自定義一個過濾器去繼承UsernamePasswordAuthenticationFilter然後重寫這兩個方法吧

代碼的邏輯還是很清晰的,我就不去講解了。

現在重點來了,Spring Security怎麼知道我們要去調用自己的UserService和自定義的過濾器呢?所以我們需要配置一下,這也是使用Spring Security的一個核心——>配置類

在配置類中,配置了認證用戶的來源和添加了自定義的過濾器。這樣就可以實現登錄的功能了。

可以看到,現在已經成功登錄了,但是這個/login是從哪兒來的呢,這個是Spring Security自己提供的,用戶名的鍵必須是」username「,密碼的鍵必須是 」password「,提交方式必須是POST。

總結一下,實現登錄的功能需要做哪些操作:

認證用戶實現UserDetails接口用戶來源的Service實現UserDetailsService接口,實現loadUserByUsername()方法,從資料庫中獲取數據實現自己的過濾器繼承UsernamePasswordAuthenticationFilter,重寫attemptAuthentication()和successfulAuthentication()方法實現自己的邏輯Spring Security的配置類繼承自WebSecurityConfigurerAdapter,重寫裡面的兩個config()方法如果使用RSA非對稱加密,就準備好RSA的配置類,然後在啟動類中加入註解將其加入IOC容器中資源伺服器權限校驗

在這一小節,我們要實現去訪問資源伺服器中的資源,並進行鑑權的操作。在父工程SpringSecirityDemo中再創建一個模塊recourse_server。因為我們現在並不需要從資料庫中獲取用戶信息。所以就不需要自己去定義Service和Mapper了。也不需要登錄的過濾器了。下面這張目錄結構圖是資源服務工程所需要的所有東西。

SysRole上一節中用到了但是沒有詳細說明。這個類是用來封裝角色信息的,做鑑權的時候用的,實現了GrantedAuthority接口:

裡面實現了getAuthority方法,直接返回roleName即可。roleName是角色名。

客戶端將Token傳到資源伺服器中,伺服器需要對Token進行校驗並取出其中的載荷信息。所以我們可以自定義一個過濾器繼承自BasicAuthenticationFilter,然後重寫doFilterInternal()方法,實現自己的邏輯。

在這段代碼中,先是從請求頭中獲取"Authorization"的值,如果值未null或者不是以我們規定的 「RobodToken 」 開頭就說明不是我們設置的Token,就是沒登錄,提示用戶登錄。有Token的話就調用JwtUtils.getInfoFromToken()去驗證並獲取載荷的內容。驗證通過的話就在Authentication的構造方法中把角色信息傳進去,然後交給其它過濾器去執行即可。

私鑰應該只保存在認證伺服器中,所以資源伺服器中只要存公鑰就可以了。

接下來就是Spring Security核心的配置文件了

這裡面有個註解 @EnableGlobalMethodSecurity(securedEnabled = true),這個註解的意思是開啟權限控制的註解支持。然後添加了自定義的Token解析過濾器。最後在需要進行權限控制的方法上添加註解即可

好了,這樣findAll方法就需要有"ROLE_PRODUCT"權限才能訪問。我們來測試一下:

登錄成功之後,響應頭中有伺服器返回的Token信息,把它複製下來,然後添加到我們請求的請求頭中。

可以看到,現在已經成功訪問到資源了。再來換個沒有權限的用戶登錄測試一下:

請求被拒絕了,說明權限控制功能是沒有問題的。總結一下步驟:

封裝權限信息的類實現GrantedAuthority接口,並實現裡面的getAuthority()方法實現自己的Token校驗過濾器繼承自BasicAuthenticationFilter,並重寫doFilterInternal()方法,實現自己的業務邏輯編寫Spring Security的配置類繼承WebSecurityConfigurerAdapter,重寫configure()方法添加自定義的過濾器,並添加@EnableGlobalMethodSecurity(securedEnabled = true)註解開啟註解權限控制的功能如果使用RSA非對稱加密,就準備好RSA的配置類,然後在啟動類中加入註解將其加入IOC容器中,注意這裡不要只要配置公鑰即可總結

SpringBoot 整合 Spring Security到這裡就結束了。文章只是簡單的說了一下整合的流程,很多其它的東西都沒有說,比如各個過濾器都有什麼作用等。還有,這裡採用的認證伺服器和資源伺服器分離的方式,要是集成在一起也是可以的。類似的問題還有很多,小夥伴們就自行研究吧。問了讓文章不會太臃腫,很多代碼都沒有貼出來,有需要的小夥伴點擊下面的連結就可以下載了。

相關焦點

  • springboot+springsecurity實現前後端分離簡單實現!
    通過各種方式學習springsecurity,在B站、騰訊課堂、網易課堂、慕課網沒有springsecurity的前後端分離的教學視頻,那我就去csdn去尋找springsecurity博客,發現幾個問題:要麼就是前後端不分離,要麼就是通過內存方式讀取數據,而不是通過資料庫的方式讀取數據,要麼就是大佬們給的代碼不全、把代碼講的太繞,關鍵部分沒有注釋
  • 一篇帶給你SpringBoot + Spring Security入門
    ; import org.springframework.security.crypto.; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.
  • Springboot 項目搭建入門
    項目由於其自動配置了很多的依賴,簡化了開發者的配置,因此加快了開發者的開發速度,但是如果對spring 底層等不太了解的人,還是有些懵的,建議大家學習spring 之後再來使用spring boot項目來搭建。
  • springcloud五大組件
    首先我們來看springcloud是什麼?它是微服務架構集大成者,基於springboot構建,可以將一系列優秀組件進行完美整合。對熟悉的程式設計師來說,上手不麻煩,對新手來說,就需要了解springcloud架構再去學習。
  • Springboot學習二:Springboot手動搭建第一個web工程
    功能使用springboot手動搭建一個web工程。搭建步驟創建一個maven工程(簡單骨架)。完成pom中打包類型是pom,pom中繼承springboot資源。創建子工程,繼承自定義父工程(默認也繼承了springboot) 由於父工程繼承了springboot,子工程也具備開發springboot能力,由springboot傳遞過來的各種資源屬性。在pom文件中(子工程)依賴一個開發web應用的資源。
  • 基於SpringBoot Cloud構建的一個商城項目源碼分享
    基於springboot cloud構建的一個商城項目,包括前端,後端和h5應用,小程序,作為zscat應用實踐的模板項目。基於SpringBoot2.x、SpringCloud和SpringCloudAlibaba並採用前後端分離的企業級微服務敏捷開發系統架構。
  • springboot的jar為何能獨立運行
    能獨立運行的jar文件在開發springboot應用時,通過java -jar命令啟動應用是常用的方式,今天就來一起了解這個簡單操作背後的技術;開發demo開發一個springboot應用作為本次研究的對象,對應的版本信息如下:
  • Springboot學習:Springboot的特點及核心功能概述
    圖片來源於網絡2.Springboot的特點2.1獨立運行spring容器圖片來源於網絡2.3簡化依賴Springboot能夠實現它的獨有的特點,是因為它在spring基礎之上擴展了非常龐大的量的代碼導致要想使用springboot必須依賴大量的資源.這樣極其不方便的。所以Springboot為開發者準備來的豐富環境的簡化依賴。
  • 基礎篇:Spring Boot入門體驗(圖文教程)
    用大佬的話來理解,就是 spring boot 其實不是什麼新的框架,它默認配置了很多框架的使用方式,就像 maven 整合了所有的 jar 包,spring boot 整合了所有的框架,總結一下及幾點:(1)為所有 Spring 開發提供一個更快更廣泛的入門體驗。(2)零配置。無冗餘代碼生成和XML 強制配置,遵循「約定大於配置」 。
  • 黑馬程式設計師:SpringBoot教程,SpringBoot高級之原理分析
    創建一個模塊,springboot-condition:@SpringBootApplicationpublic class SpringbootConditionApplication {public static void main(String[] args
  • 史上最全spring boot實戰文檔,吃透這些,面試幹掉80%對手
    最大的重要性是:springcloud是一個基於springboot實現的一系 列框架的集合,用來提供全局的服務治理方案。springcloud要基於springboot來實現,離不開springboot。如果要學習源碼,當然還是SpringBoot最適合不過了。
  • 全網最細緻的SpringBoot實戰教程,超適合新手小白入坑學習
    lt;artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies>spring-boot-starter:spring-boot場景啟動器spring-boot-starter-web
  • Spring Boot實現定時任務新解,你是否能get到?
    在日常的開發過程中經常使用到定時任務,在springMVC的開發中,經常和quartz框架進行集成使用,但在springboot中沒有這麼做,而是使用了java的線程池來實現定時任務。一、概述在springboot中使用定時任務非常簡單,只需要簡單的幾步即可完成。
  • 從零搭建 Spring Cloud 服務(超詳細)
    另外SpringCloud需要基於springboot搭建。2.1 引入Spring Boot相關依賴 這裡的springboot用的是1.5.7版本引入Spring Cloud相關依賴 這裡為Edgware.SR52.1 工程初始化配置在Idea中創建工程
  • Springboot+MybatisPlus高效實現增刪改查
    本文分為以下幾個部分講解:引入Mybatis-Plus依賴代碼生成器配置Mybatis-PlusCURD示例條件構造分頁擴展功能一、引入Mybatis-Plus依賴本文示例使用maven作為依賴管理,在pom.xml文件引入springboot和Mybatis-plus
  • Spring全家桶、Dubbo、分布式、消息隊列後端必備全套開源項目
    事務管理《Spring Boot 分布式事務 Seata 入門》 對應 lab-52安全控制《Spring Boot 安全框架 Spring Security 入門》 對應 lab-01-spring-security
  • 騰訊T7架構師至今還在鑽研的SpringBoot從構建小系統到架構大系統
    前言個人認為,springboot是隨著Spring4.0出生的, 他的出現,目的是為了解決Spring這麼多年來的發展導致本身的笨重、各類繁瑣及與第三方框架整合的配置文件,對於碼農或項目團隊來說,是不希望見到的。因此springboot的出現是為了解決spring多年發展帶來的弊端。
  • pacebox-springboot 1.1.5 發布,java 生態框架
    pacebox-springboot 融合封裝已發布,旨在提供快速開發腳手架、打造更好的開源生態環境。
  • 芋道 Spring Boot JPA 入門(一)之快速入門
    >org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>