使用SpringAop切面編程通過Spel表達式實現Controller權限控制

目錄

  • 參考
  • 一、概念
    • SpEL表達式
  • 二、開發
    • 引入包
    • 定義注解
    • 定義切面
    • 定義用戶上下文
  • 三、測試
    • 新建Service在方法上注解
    • 新建Service在類上注解
    • 運行

參考

SpringBoot:SpEL讓復雜權限控制變得很簡單

一、概念

對于在Springboot中,利用自定義注解+切面來實現接口權限的控制這個大家應該都很熟悉,也有大量的博客來介紹整個的實現過程,整體來說思路如下:

  • 自定義一個權限校驗的注解,包含參數value
  • 配置在對應的接口上
  • 定義一個切面類,指定切點
  • 在切入的方法體里寫上權限判斷的邏輯

SpEL表達式

本文前面提到SpEL,那么到底SpEL是啥呢? SpEL的全稱為Spring Expression Language,即Spring表達式語言。是Spring3.0提供的。他最強大的功能是可以通過運行期間執行的表達式將值裝配到我們的屬性或構造函數之中。如果有小伙伴之前沒有接觸過,不太理解這句話的含義,那么不要緊,繼續往下看,通過后續的實踐你就能明白他的作用了。

二、開發

引入包

  	<!--spring aop + aspectj--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.0.8.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><!--spring aop + aspectj-->

定義注解

我們僅需要定義一個value屬性用于接收表達式即可。


@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuth {/***** permissionAll()-----只要配置了角色就可以訪問* hasPermission("MENU.QUERY")-----有MENU.QUERY操作權限的角色可以訪問* hasAnyPermission("MENU.QUERY","MENU.ADD")-----有MENU.QUERY操作權限的角色可以訪問* permitAll()-----放行所有請求* denyAll()-----只有超級管理員角色才可訪問* hasAuth()-----只有登錄后才可訪問* hasTimeAuth(1,,10)-----只有在1-10點間訪問* hasRole(‘管理員’)-----具有管理員角色的人才能訪問* hasAnyRole(‘管理員’,'總工程師')-----同時具有管理員* hasAllRole(‘管理員’,'總工程師')-----同時具有管理員、總工程師角色的人才能訪問、總工程師角色的人才能訪問** Spring el* 文檔地址:<a href="https://docs.spring.io/spring/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions">...</a>*/String value();}

定義切面

我們就需要定義切面了。這里要考慮一個點。我們希望的是如果方法上有注解,則對方法進行限制,若方法上無注解,單是類上有注解,那么類上的權限注解對該類下所有的接口生效。因此,我們切點的話要用@within注解

// 方式一
@Pointcut(value = "execution(* com.edevp.spring.spel.auth..*.*(..))")
// 方式二 直接切入注解
@Pointcut("@annotation(com.edevp.spring.spel.auth.annotation.PreAuth) || @within(com.edevp.spring.spel.auth.annotation.PreAuth)")

/***  必須的注解* @create 2023/5/24*/
@Component
@Aspect
@Slf4j
public class AuthAspect {@Resourceprivate AuthContext authContext;@PostConstructpublic void init(){log.info("鑒權切面初始化");}/*** Spel解析器 關鍵點來了。這里我們要引入SpEL。*/private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();//    @Pointcut(value = "execution(* com.edevp.spring.spel.auth..*.*(..))")@Pointcut("@annotation(com.edevp.spring.spel.auth.annotation.PreAuth) || @within(com.edevp.spring.spel.auth.annotation.PreAuth)")private void beforePointcut(){//切面,方法里的內容不會執行}/*** 前置通知* @param joinPoint 切點*/@Before(value = "beforePointcut()")public void before(JoinPoint joinPoint){//@Before是在方法執行前無法終止原方法執行log.info("前置通知。。。"+joinPoint);if (handleAuth(joinPoint)) {return;}throw new NoAuthException("沒權限");}/*** 環繞通知* @param joinPoint 切點* @return Object*/@Around("beforePointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//@Before是在方法執行前無法終止原方法執行log.info("環繞通知。。。"+joinPoint);return joinPoint.proceed();}/*** 判斷是否有權限* @param point 切點* @return boolean*/@SuppressWarnings("unchecked")private boolean handleAuth(JoinPoint point) {MethodSignature ms = point.getSignature() instanceof MethodSignature? (MethodSignature) point.getSignature():null;assert ms != null;Method method = ms.getMethod();// 讀取權限注解,優先方法上,沒有則讀取類PreAuth preAuth = method.getAnnotation(PreAuth.class);if(preAuth == null){preAuth = (PreAuth) ms.getDeclaringType().getDeclaredAnnotation(PreAuth.class);}// 判斷表達式String condition = preAuth.value();if (StringUtil.isNotBlank(condition)) {Expression expression = EXPRESSION_PARSER.parseExpression(condition);StandardEvaluationContext context = new StandardEvaluationContext(authContext);// 獲取解析計算的結果return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));}return false;}
}

定義用戶上下文

有的同學會問,你權限校驗的邏輯呢?別急,關鍵點在這:StandardEvaluationContext context = new StandardEvaluationContext(authContext );在上文代碼中找到了吧。

這個AuthFun就是我們進行權限校驗的對象。所以呢,我們還得在定義一下這個對象。進行具體的權限校驗邏輯處理,這里定的每一個方法都可以作為表達式在權限注解中使用。代碼如下:
方法對應PreAuth中的方法字符串

@Component
public class AuthContext {private static final ThreadLocal<UserContext> USER_CONTEXT_THREAD_LOCAL = new NamedThreadLocal<>("user context");public static void setUserContext(UserContext user){USER_CONTEXT_THREAD_LOCAL.set(user);}public static UserContext getUserContext(){return USER_CONTEXT_THREAD_LOCAL.get();}public static void removeUserContext(){USER_CONTEXT_THREAD_LOCAL.remove();}/*** 判斷角色是否具有接口權限** @param permission 權限編號,對應菜單的MENU_CODE* @return {boolean}*/public boolean hasPermission(String permission) {//TODOreturn hasAnyPermission(permission);}/*** 判斷角色是否具有接口權限** @param permission 權限編號,對應菜單的MENU_CODE* @return {boolean}*/public boolean hasAllPermission(String... permission) {//TODOfor (String r : permission) {if (!hasPermission(r)) {return false;}}return true;}/*** 放行所有請求** @return {boolean}*/public boolean permitAll() {return true;}/*** 只有超管角色才可訪問** @return {boolean}*/public boolean denyAll() {return hasRole("admin");}/*** 是否有時間授權** @param start 開始時間* @param end   結束時間* @return {boolean}*/public boolean hasTimeAuth(Integer start, Integer end) {/*Integer hour = DateUtil.hour();return hour >= start && hour <= end;*/return true;}/*** 判斷是否有該角色權限** @param role 單角色* @return {boolean}*/public boolean hasRole(String role) {return hasAnyRole(role);}/*** 判斷是否具有所有角色權限** @param role 角色集合* @return {boolean}*/public boolean hasAllRole(String... role) {for (String r : role) {if (!hasRole(r)) {return false;}}return true;}/*** 判斷是否有該角色權限** @param roles 角色集合* @return {boolean}*/public boolean hasAnyRole(String... roles) {UserContext user = getUser();if(user!= null){return hasAnyStr(user.getRoles(),roles);}return false;}/*** 判斷是否有該角色權限** @param authorities 角色集合* @return {boolean}*/public boolean hasAnyPermission(String... authorities) {UserContext user = getUser();if(user!= null){return hasAnyStr(user.getAuthorities(),authorities);}return false;}public boolean hasAnyStr(String hasStrings,String... strings) {if(StringUtil.isNotEmpty(hasStrings)){String[] roleArr = hasStrings.split(SymbolConstant.COMMA);return Arrays.stream(strings).anyMatch(r-> Arrays.asList(roleArr).contains(r));}return false;}public UserContext getUser(){UserContext o = AuthContext.getUserContext();if(o != null){return  o;}return null;}}

三、測試

在使用的時候,我們只需要在類上或者接口上,加上@PreAuth的直接,value值寫的時候要注意一下,value應該是我們在AuthContext 類中定義的方法和參數,如我們定義了解析方法hasAllRole(String… role),那么在注解中,我們就可以這樣寫@PreAuth(“hasAllRole(‘角色1’,‘角色2’)”),需要注意的是,參數要用單引號包括。
根據上面的實際使用,可以看到。SpEL表達式解析將我們注解中的"hasAllRole(‘角色1’,‘角色2’)"這樣的字符串,給動態解析為了hasAllRole(參數1,參數1),并調用我們注冊類中同名的方法。

新建Service在方法上注解

@Slf4j
@Component
public class AuthTestMethodService {@PreAuth("hasRole('admin')")public void testHasRole(){log.info("測試 hasRole('admin')");}@PreAuth("hasAnyRole('admin','test')")public void testHasAnyRole(){log.info("測試 testHasAnyRole('admin')");}@PreAuth("hasAllRole('admin','test')")public void testHasAllRole(){log.info("測試 testHasAllRole('admin')");}@PreAuth("hasPermission('sys:user:add')")public void testHasPermission(){log.info("測試 hasPermission('admin')");}
}

新建Service在類上注解

@Slf4j
@Component
@PreAuth("hasRole('admin')")
public class AuthTestClassService {public void testHasRole(){log.info("測試 hasRole('admin')");}}

運行


@FunctionalInterface
public interface Executer {/*** 執行*/void run();
}
...
...@SpringBootTest
public class AuthTest {@Resourceprivate AuthTestMethodService authTestService;@Resourceprivate AuthTestClassService authTestClassService;@Testvoid testInit(){AuthTestMethodService authTestService2 = new AuthTestMethodService();authTestService2.testHasRole();System.out.println("================");UserContext user = new UserContext();user.setRoles("admin,test");/* testAuth(user,()->{authTestService.testHasRole();});*/testAuth(user,()->{authTestService.testHasRole();authTestService.testHasAllRole();});user.setRoles("test");testAuth(user,()->{authTestService.testHasAnyRole();authTestService.testHasAllRole();});}@Testvoid testClass(){System.out.println("================");UserContext user = new UserContext();user.setRoles("admin,test");testAuth(user,()->{authTestClassService.testHasRole();});user.setRoles("test");testAuth(user,()->{authTestClassService.testHasRole();});}private void testAuth(UserContext user, Executer executer) {AuthContext.setUserContext(user);// 執行try{executer.run();}catch (Exception e){throw e;}finally {AuthContext.removeUserContext();}}}

在這里插入圖片描述

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

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

相關文章

opencv實戰項目 手勢識別-手勢音量控制(opencv)

本項目是使用了谷歌開源的框架mediapipe&#xff0c;里面有非常多的模型提供給我們使用&#xff0c;例如面部檢測&#xff0c;身體檢測&#xff0c;手部檢測等。 手勢識別系列文章 1.opencv實現手部追蹤&#xff08;定位手部關鍵點&#xff09; 2.opencv實戰項目 實現手勢跟蹤…

8月14日,每日信息差

1、FF正式交付首輛FF 91 2.0 Futurist Alliance給塔尖用戶 2、消息稱iPhone SE 4設計基于iPhone 14&#xff0c;但仍是后置單攝像頭 3、阿聯酋力推電動汽車發展。該政策將作為一個監管框架&#xff0c;明確電動汽車充電站等基礎設施建設的標準&#xff0c;并推動全國標準統一…

Jay17 2023.8.12日報

8.12 今天做了2題&#xff0c;CTFshow 紅包挑戰8&#xff08;PHP create_function()&#xff09;和BUU [RoarCTF 2019]Easy Java&#xff08;web.xml泄露&#xff09;。 此外一直在打NepCTF&#xff0c;出了一題&#xff08;ez_java_checkin&#xff09;簡單了解了java中shri…

Kafka消息隊列學習(一)

文章目錄 概述核心概念生產者示例同步 / 異步發送消息生產者參數配置ack-確認機制retries - 重試次數compression_type - 消息壓縮類型 分區機制分區策略 消費者消息有序性提交和偏移量偏移量提交方式手動提交 高可用設計 SpringBoot集成Kafka基本使用傳遞對象消息 概述 核心概…

HTTP之cookie基礎學習

目錄 Cookie 什么是Cookie Cookie分類 Cookie版本 Cookie工作原理 Cookie詳解 創建cookie cookie編碼 cookie過期時間選項 Cookie流程 Cookie使用 會話管理 個性化信息 記錄用戶的行為 Cookie屬性 domain選項 path選項 secure選項 cookie…

帶著問題學習分布式系統

寫在前面 聽過很多道理&#xff0c;卻依然過不好這一生。 看過很多關于學習的技巧、方法&#xff0c;卻沒應用到自己的學習中。 隨著年紀變大&#xff0c;記憶力越來越差&#xff0c;整塊的時間也越來越少&#xff0c;于是&#xff0c;越來越希望能夠更高效的學習。學習是一種習…

香港大學余濤組推出開源XLANG Agent!支持三種Agent模式

作者 |小戲、ZenMoore 一個新的未來又逐漸開始從理論走向現實走到我們身邊了。 語言的意義在于使用&#xff0c;而從 ChatGPT 以來這些大規模語言模型的意義&#xff0c;也必然絕不止于 Chat&#xff0c;在四個月前&#xff0c;我們介紹了清華大學關于工具學習的綜述《清華發布…

Python-OpenCV中的圖像處理-圖像特征

Python-OpenCV中的圖像處理-圖像特征 圖像特征Harris角點檢測亞像素級精度的角點檢測Shi-Tomasi角點檢測SIFT(Scale-Invariant Feature Transfrom)SURF(Speeded-Up Robust Features) 圖像特征 特征理解特征檢測特征描述 Harris角點檢測 cv2.cornerHarris(img, blockSize, ks…

海格里斯HEGERLS四向穿梭車倉儲解決方案在電子商務行業中的應用

隨著現代物流&#xff0c;尤其是智能化物流的飛速發展&#xff0c;河北沃克金屬制品有限公司看到了智能物流領域背后的巨大價值和市場空間&#xff0c;深知物流與供應鏈對企業發展的重要性。于是&#xff0c;引進了先進的高科技智能技術—HEGERLS四向穿梭車技術&#xff0c;并迅…

【日常積累】Linux下文件亂碼解決

linux下刪除亂碼文件、目錄 由于編碼原因&#xff0c;在linux服務器上上傳、創建中文文件或目錄時&#xff0c;會產生亂碼&#xff0c;如果想刪除它&#xff0c;有時候發現用rm命令是刪除不了的 這種情況下&#xff0c;用find命令可以刪除亂碼的文件或目錄。 首先進入亂碼文件…

docker 網絡訪問診斷

本地docker開啟nginx服務等&#xff0c; 發現linux系統重啟之后&#xff0c;無法訪問&#xff0c; 進入容器內部&#xff0c;發現可以訪問 但是容器外部&#xff0c;映射端口無法訪問&#xff1b; 診斷之前&#xff0c;發現docker0沒有IP綁定 rootbook:/etc/docker# ip addr …

自制手寫機器人

寫字機器人模擬在畫圖板上寫字效果 寫了一套寫字機器人代碼&#xff0c;有多種字體可供選擇&#xff0c;需要的朋友私信獲取代碼和軟件

Spring5學習筆記— 工廠高級特性

?作者簡介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;熱愛Java后端開發者&#xff0c;一個想要與大家共同進步的男人&#x1f609;&#x1f609; &#x1f34e;個人主頁&#xff1a;Leo的博客 &#x1f49e;當前專欄&#xff1a; Spring專欄 ?特色專欄&#xff1a; M…

創建型模式-原型模式

文章目錄 一、原型模式1. 概述2. 結構3. 實現4. 案例1.5 使用場景1.6 擴展&#xff08;深克隆&#xff09; 一、原型模式 1. 概述 用一個已經創建的實例作為原型&#xff0c;通過復制該原型對象來創建一個和原型對象相同的新對象。 2. 結構 原型模式包含如下角色&#xff1a; …

微服務架構和分布式架構的區別

微服務架構和分布式架構的區別 有&#xff1a;1、含義不同&#xff1b;2、概念層面不同&#xff1b;3、解決問題不同&#xff1b;4、部署方式不同&#xff1b;5、耦合度不同。其中&#xff0c;含義不同指微服務架構是一種將一個單一應用程序開發為一組小型服務的方法&#xff…

使用windows搭建WebDAV服務,并內網穿透公網訪問【無公網IP】

文章目錄 1. 安裝IIS必要WebDav組件2. 客戶端測試3. 使用cpolar內網穿透&#xff0c;將WebDav服務暴露在公網3.1 打開Web-UI管理界面3.2 創建隧道3.3 查看在線隧道列表3.4 瀏覽器訪問測試 4. 安裝Raidrive客戶端4.1 連接WebDav服務器4.2 連接成功4.2 連接成功 1. Linux(centos8…

【Vue-Router】路由入門

路由&#xff08;Routing&#xff09;是指確定網站或應用程序中特定頁面的方式。在Web開發中&#xff0c;路由用于根據URL的不同部分來確定應用程序中應該顯示哪個內容。 構建前端項目 npm init vuelatest //或者 npm init vitelatest安裝依賴和路由 npm install npm instal…

TCP重連 - 筆記

1 C++ TCP/IP 關于tcp斷線重連的問題 C++ TCP/IP 關于tcp斷線重連的問題_c++ 斷線重連_Bug&猿柒。的博客-CSDN博客 2 C++基礎--完善Socket C/S ,實現客戶端,服務器端斷開重連 https://www.cnblogs.com/kingdom_0/articles/2571727.html 3 C++實現Tcp通信(考慮客戶…

ATF BL1 UFS初始化簡單分析

ATF BL1 UFS初始化分析 1 ATF的下載鏈接2 ATF BL1 UFS 初始化簡易流程圖3 ATF BL1 ufs初始化簡單過程分析3.1 調用過程3.2 hikey960_ufs_init3.3 dw_ufs_init3.3 ufs_init 以海思hikey960為例來介紹&#xff0c;簡單介紹在ATF BL1階段的初始化處理。 1 ATF的下載鏈接 https:/…

藍帽杯 取證2022

網站取證 網站取證_1 下載附件 并解壓 得到了一個文件以及一個壓縮包 解壓壓縮包 用火絨查病毒 發現后門 打開文件路徑之后 發現了一句話木馬 解出flag 網站取證_2 讓找數據庫鏈接的明文密碼 打開www文件找找 查看數據庫配置文件/application/database.php&#xff08;CodeI…