spring boot 3.x版本中集成spring security 6.x版本進行實現動態權限控制解決方案

一、背景

最近在進行項目從jdk8spring boot 2.7.x版本技術架構向jdk17spring boot 3.3.x版本的代碼遷移,在遷移過程中,發現spring boot 3.3.x版本依賴的spring security版本已經升級6.x版本了,語法上和spring security 5.x版本有很多地方不兼容,因此記錄試一下spring boot 3.3.x版本下,spring security 6.x的集成方案。

二、技術實現

1. 創建spring boot 3.3.x版本項目

spring boot 3.3.x版本對jdk版本要求較高,我這里使用的是jdk17,不久前,jdk21也已經發布了,可以支持虛擬線程,大家也可以使用jdk21

設置好jdk版本以后,新建項目,導入項目需要的相關依賴:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.1</version></parent><groupId>com.j.ss</groupId><artifactId>spring-secrity6-spring-boot3-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>spring-secrity6-spring-boot3-demo</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>

2. 創建兩個測試接口

  • 創建兩個接口用于測試,源碼參考如下

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class SecurityController {@GetMapping("/hello")public String hello() {return "hello, spring security.";}@PostMapping("/work")public String work() {return "I am working.";}}
    
  • 啟動項目,測試一下接口是否正常

    • hello接口

    • work接口
      在這里插入圖片描述

3. 引入spring-boot-starter-security依賴

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

引入spring-boot-starter-security依賴以后,此時訪問接口,會有未授權問題。

在這里插入圖片描述

4. 定義UserDetailsManager實現類

spring security框架會自動使用UserDetailsManagerloadUserByUsername方法進行用戶加載,在加載用戶以后,會在UsernamePasswordAuthenticationFilter過濾器中的attemptAuthentication方法中,進行前端輸入的用戶信息和加載的用戶信息進行信息對比。

import lombok.extern.java.Log;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;@Component
@Log
public class MyUserDetailsManager implements UserDetailsManager {@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {/*** 這里為了演示方便,模擬從數據庫查詢,直接設置一下權限*/log.info("query user from db!");return queryFromDB(username);}private static UserDetails queryFromDB(String username) {GrantedAuthority authority = new SimpleGrantedAuthority("testRole");List<GrantedAuthority> list = new ArrayList<>();list.add(authority);return new User("jack", // 用戶名稱new BCryptPasswordEncoder().encode("123456"), //密碼list      //權限列表);}
}

5. 定義權限不足處理邏輯

用戶在訪問沒有權限的接口時,會拋出異常,spring security允許我們自己這里這種異常,我這里就是模擬一下權限不足的提示信息,不做過多處理。

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.io.PrintWriter;@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {//登陸狀態下,權限不足執行該方法response.setStatus(200);response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter printWriter = response.getWriter();String body = "403,權限不足!";printWriter.write(body);printWriter.flush();}
}

6. 定義未登錄情況處理邏輯

當用戶沒有登錄情況下,訪問需要權限的接口時,會拋出異常,spring security允許我們自定義處理邏輯,這里未登錄就直接拋出401,提示用戶登錄。

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {//驗證為未登陸狀態會進入此方法,認證錯誤response.setStatus(401);response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter printWriter = response.getWriter();String body = "401, 請先進行登錄!";printWriter.write(body);printWriter.flush();}
}

7. 定義自定義動態權限檢驗處理邏輯

在請求接口進行安全訪問的時候,我們可以指定訪問接口需要的角色,但是實際應用中,為了滿足系統的靈活性,我們往往需要自定義動態權限的校驗邏輯。

import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;import java.util.Collection;
import java.util.function.Supplier;@Component
public class MyAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {/*** @param authentication the {@link Supplier} of the {@link Authentication} to check* @param object         the {@link T} object to check* @return*/@Overridepublic AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {// 獲取訪問urlString requestURI = object.getRequest().getRequestURI();// 模擬從數據庫或者緩存里面查詢擁有當前URI的權限的角色String[] allRole = query(requestURI);// 獲取當前用戶權限Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();// 判斷是否擁有權限for (String role : allRole) {for (GrantedAuthority r : authorities) {if (role.equals(r.getAuthority())) {return new AuthorizationDecision(true); // 返回有權限}}}return new AuthorizationDecision(false); //返回沒有權限}/*** 查詢當前擁有對應url的權限的角色** @param requestURI* @return*/private String[] query(String requestURI) {return new String[]{"testRole"};}
}

8. 定義安全訪問統一入口

在統一入口,我們可以做一些統一的邏輯,比如前后端分離的情況下,進行token內容的解析,這里我只是用代碼模擬演示一下,方便大家理解。

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.java.Log;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;@Component
@Log
public class MyAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token"); // 前后端分離的時候獲取tokenif (StringUtils.hasText(token)) { // 如果token不為空,則需要解析出用戶信息,填充到當前上下文中UsernamePasswordAuthenticationToken authentication = getUserFromToken(token);SecurityContextHolder.getContext().setAuthentication(authentication);if (log.isLoggable(Level.INFO)) {log.info("set authentication");}} else {if (log.isLoggable(Level.INFO)) {log.info("user info is null.");}}filterChain.doFilter(request, response);}private UsernamePasswordAuthenticationToken getUserFromToken(String token) {GrantedAuthority authority = new SimpleGrantedAuthority(token);List<GrantedAuthority> list = new ArrayList<>();list.add(authority);User user = new User("jack", // 用戶名稱new BCryptPasswordEncoder().encode("123456"), //密碼list      //權限列表);UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());usernamePasswordAuthenticationToken.setDetails(user);return usernamePasswordAuthenticationToken;}
}

9. 編寫spring security配置類

當所有準備工作,做好以后,下面就是編寫spring security的配置類了,使我們的相關配置生效。

import com.j.ss.MyAccessDeniedHandler;
import com.j.ss.MyAuthenticationEntryPoint;
import com.j.ss.MyAuthenticationFilter;
import com.j.ss.MyAuthorizationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;/*** @Configuration 注解表示將該類以配置類的方式注冊到spring容器中*/
@Configuration
/*** @EnableWebSecurity 注解表示啟動spring security*/
@EnableWebSecurity
/*** @EnableMethodSecurity 注解表示啟動全局函數權限*/
@EnableMethodSecurity
public class WebSecurityConfig {/*** 權限不足處理邏輯*/@Autowiredprivate MyAccessDeniedHandler accessDeniedHandler;/*** 未授權處理邏輯*/@Autowiredprivate MyAuthenticationEntryPoint authenticationEntryPoint;/*** 訪問統一處理器*/@Autowiredprivate MyAuthenticationFilter authenticationTokenFilter;/*** 自定義權限校驗邏輯*/@Autowiredprivate MyAuthorizationManager myAuthorizationManager;/*** spring security的核心過濾器鏈** @param httpSecurity* @return*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {// 定義安全請求攔截規則httpSecurity.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {authorizationManagerRequestMatcherRegistry.requestMatchers("/hello").permitAll() // hello 接口放行,不進行權限校驗.anyRequest()// .hasRole() 其他接口不進行role具體校驗,進行動態權限校驗.access(myAuthorizationManager); // 動態權限校驗邏輯})// 前后端分離,關閉csrf.csrf(AbstractHttpConfigurer::disable)// 前后端分離架構禁用session.sessionManagement(httpSecuritySessionManagementConfigurer -> {httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS);})// 訪問異常處理.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {httpSecurityExceptionHandlingConfigurer.accessDeniedHandler(accessDeniedHandler);})// 未授權異常處理.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(authenticationEntryPoint);}).headers(httpSecurityHeadersConfigurer -> {// 禁用緩存httpSecurityHeadersConfigurer.cacheControl(HeadersConfigurer.CacheControlConfig::disable);httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable);});// 添加入口filter, 前后端分離的時候,可以進行token解析操作httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);return httpSecurity.build();}/*** 明文密碼加密** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 忽略權限校驗** @return*/@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web -> web.ignoring().requestMatchers("/hello"));}}

三、 功能測試

上述代碼編寫完成以后,啟動項目,下面進行功能測試。

1. 忽略權限校驗測試

訪問/hello接口

在這里插入圖片描述

可以看到,此時接口在無登錄信息的情況下,也可以正常訪問的。

2. 無權限測試

同樣的,我們直接訪問/work接口

在這里插入圖片描述

可以看到,此時提醒我們需要登錄了。

3. 有權限測試

再次訪問/work接口,模擬已經登錄,并擁有對應的權限。

在這里插入圖片描述

可以看到,我們模擬有testRole權限,此時訪問是正常的。

4. 權限不足測試

再次訪問/work接口,模擬已經登錄,但擁有錯誤的權限。

在這里插入圖片描述

可以看到,此時報出了權限不足的異常。

四、寫在最后

上面的案例只是演示,spring security的實際應用,應該根據具體項目權限要求來進行合理實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/42264.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/42264.shtml
英文地址,請注明出處:http://en.pswp.cn/web/42264.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql中存儲引擎簡介、修改、查詢、選擇

場景 數據庫存儲引擎 數據庫存儲引擎是數據庫底層軟件組件&#xff0c;數據庫管理系統&#xff08;DBMS &#xff09;使用數據引擎進行創建、查詢、更新和刪除數據的操作。 不同的存儲引擎提供不同的存儲機制、索引技巧、鎖定水平等功能&#xff0c;使用不同的存儲引擎還可以…

【C++報錯已解決】Invalid Use of ‘this’ Pointer

&#x1f3ac; 鴿芷咕&#xff1a;個人主頁 &#x1f525; 個人專欄: 《C干貨基地》《粉絲福利》 ??生活的理想&#xff0c;就是為了理想的生活! 文章目錄 引言 一、問題描述1.1 報錯示例1.2 報錯分析1.3 解決思路 二、解決方法2.1 方法一&#xff1a;修正‘this’指針使用2…

React+TS前臺項目實戰(二十六)-- 高性能可配置Echarts圖表組件封裝

文章目錄 前言CommonChart組件1. 功能分析2. 代碼詳細注釋3. 使用到的全局hook代碼4. 使用方式5. 效果展示 總結 前言 Echarts圖表在項目中經常用到&#xff0c;然而&#xff0c;重復編寫初始化&#xff0c;更新&#xff0c;以及清除實例等動作對于開發人員來說是一種浪費時間…

LVS-DR負載均衡

LVS-DR負載均衡 LVS—DR工作模式 原理 客戶端訪問調度器的VIP地址&#xff0c;在路由器上應該設置VIP跟調度器的一對一的映射關系&#xff0c;調度器根據調度算法將該請求“調度“到后端真實服務器&#xff0c;真實服務器處理完畢后直接將處理后的應答報文發送給路由器&#xf…

EDI安全:如何在2024年保護您的數據免受安全和隱私威脅

電子數據交換&#xff08;EDI&#xff09;支持使用標準化格式在組織之間自動交換業務文檔。這種數字化轉型徹底改變了業務通信&#xff0c;消除了對紙質交易的需求并加速了交易。然而&#xff0c;隨著越來越依賴 EDI 來傳輸發票、采購訂單和發貨通知等敏感數據&#xff0c;EDI …

【跨境分享】中國商家如何卷到國外?電商獨立站和電商平臺的優勢對比

為什么要選擇獨立站而不是電商平臺 對于跨境電商經營者而言&#xff0c;采取多平臺、多站點的運營策略是至關重要的戰略布局。這一做法不僅有助于分散風險&#xff0c;避免將所有投資集中于單一市場&#xff0c;從而降低“所有雞蛋置于同一籃子”的隱患&#xff0c;而且有利于拓…

【友邦保險-注冊安全分析報告】

前言 由于網站注冊入口容易被黑客攻擊&#xff0c;存在如下安全問題&#xff1a; 暴力破解密碼&#xff0c;造成用戶信息泄露短信盜刷的安全問題&#xff0c;影響業務及導致用戶投訴帶來經濟損失&#xff0c;尤其是后付費客戶&#xff0c;風險巨大&#xff0c;造成虧損無底洞…

華為od相關信息分享

2024年OD統一考試&#xff08;D卷&#xff09;完整題庫&#xff1a;華為OD機試2024年最新題庫&#xff08;Python、JAVA、C合集&#xff09; 問 1.什么是華為od&#xff1f; 答&#xff1a;OD全稱是Outsourcing Dispacth&#xff0c;即外包派遣&#xff0c;是華為和外企德科…

金蝶云蒼穹-插件開發(三)關于基礎資料/單據實體的id

基礎資料/單據實體的id 每個基礎資料和單據的實體&#xff0c;都有一個id字段&#xff0c;這個id是其一個唯一性標識&#xff0c;就類似于這個實體的身份證號一樣。通常&#xff0c;這個id用來找到指定的實體。這個id在代碼中都是long類型接收的。 如果基礎資料/單據里面有字…

Kafka日志處理:深入了解偏移量查找與切分文件

我是小米,一個喜歡分享技術的29歲程序員。如果你喜歡我的文章,歡迎關注我的微信公眾號“軟件求生”,獲取更多技術干貨! Hello, 大家好!我是你們的技術小伙伴小米,今天要和大家分享一些關于Kafka日志處理的深入知識。我們將討論如何查看偏移量為23的消息,以及Kafka日志分…

重載、覆蓋(重寫)、重定義(同名隱藏)的區別 (超詳解)

&#x1f4da; 重載&#xff08;Overloading&#xff09;、覆蓋&#xff08;Overriding&#xff09;、重定義&#xff08;Hiding&#xff09;是面向對象編程中常見的概念&#xff0c;它們分別用于描述不同情況下函數或方法的行為。 目錄 重載&#xff08;Overloading&#xff…

ST7789 linux4.x驅動

文章目錄 ST7789 linux4.x驅動設備樹配置驅動程序編譯驅動測試驅動 ST7789 linux4.x驅動 設備樹配置 pinctrl_ecspi2_cs_1: ecspi2_cs_grp-1 {fsl,pins <MX6UL_PAD_CSI_DATA01__GPIO4_IO22 0x40017059>; };pinctrl_ecspi2_1: escpi2grp {fsl,pins <MX6UL_PAD_CSI_…

RocketMQ快速入門:主從、集群模式原理及搭建實操(十一)

目錄 0. 引言1. 前備知識1.1 namesrv集群模式1.2 broker集群模式1.2 broker主從復制原理 2. 集群部署2.1 環境準備2.2 配置講解2.3 一主多從模式部署2.4 多主無從模式部署2.5 多主多從模式部署 3. 總結 0. 引言 在學習完rocketmq的基礎知識后&#xff0c;我們進入rocketmq高可…

痙攣性斜頸對生活有哪些影響?

痙攣性斜頸&#xff0c;這個名字聽起來可能并不熟悉&#xff0c;但它實際上是一種神經系統疾病&#xff0c;影響著全球數百萬人的生活質量。它以一種無法控制的方式&#xff0c;使患者的頸部肌肉發生不自主的收縮&#xff0c;導致頭部姿勢異常。對于患者來說&#xff0c;痙攣性…

和Bug較勁的第n天:[Error: Unable to open snapshot file: No such file or directory]

問題描述 最近做了一個小demo&#xff0c;基于parcel的&#xff0c;在遷移倉庫的時候發生了一個報錯 [Error: Unable to open snapshot file: No such file or directory] 原因分析&#xff1a; 在遷移倉庫的時候&#xff0c;我將項目放入了一個以中文命名的文件夾里&#xf…

模電基礎 - 信號的運算和處理

目錄 一. 簡介 二. 加法 三. 減法 四. 乘法 五. 除法 六. 總結 一. 簡介 在模電基礎中&#xff0c;信號的運算和處理是非常重要的內容。 信號的運算包括加法、減法、乘法、除法等。通過使用集成運放&#xff0c;可以很容易地實現這些運算。例如&#xff0c;利用反相輸入…

算法的幾種常見形式

算法&#xff08;Algorithm&#xff09; 算法&#xff08;Algorithm&#xff09;是指解決問題或完成任務的一系列明確的步驟或規則。在計算機科學中&#xff0c;算法是程序的核心部分&#xff0c;它定義了如何執行特定的任務或解決特定的問題。算法可以用多種方式來表示和實現…

宜春旅游集散中心展廳OLED透明屏方案設計

一、項目概述 為提升宜春旅游集散中心展廳的現代化展示水平&#xff0c;增強游客的參觀體驗&#xff0c;我們計劃在展廳的核心區域引入OLED透明屏技術。該方案旨在通過高科技的視覺呈現方式&#xff0c;將展品信息以虛擬與現實相結合的方式展現&#xff0c;打造出一個既具科技感…

谷粒商城學習筆記-22-分布式組件-SpringCloud-OpenFeign測試遠程調用

文章目錄 一&#xff0c;OpenFeign的簡介二&#xff0c;OpenFeign的使用步驟1&#xff0c;場景說明2&#xff0c;引入依賴2&#xff0c;開啟OpenFeign3&#xff0c;編寫Feign接口4&#xff0c;使用feign調用遠程接口5&#xff0c;驗證 錯誤記錄 上一節學習了注冊中心&#xff0…

鼠標錄制工具|鍵鼠軌跡錄制,實現自動辦公

利用鍵鼠錄制工具錄制固定的鼠標點擊、鍵盤輸入等操作&#xff0c;實現自動化執行固定操作&#xff0c;節省時間。鼠標錄制功能可以錄制多步驟的操作&#xff0c;將錄制的動作保存并命名&#xff0c;甚至可以編輯操作速度。下面將演示幾種生活中常見的案例&#xff0c;詳細講解…