SpringBoot(一) 如何實現AOP的許可權控制
Spring AOP是什麼
- 最近負責開發一款內部人員使用的日誌管理專案。其中涉及到了人員許可權的校驗問題。於是想到了用spring AOP的思路去實現,避免每次需要手動去新增程式碼校驗。
- Spring AOP是什麼,Aspect Oriented Programming, 面向切面程式設計,是Spring的核心之一。面向切面很明顯就是空間意義上的攔截操作。
- 比如我需要在每個業務邏輯的前後做些事情,在每次接受請求的時候,寫個日誌
log.info("=======開始接受請求=======")
- 如果我希望在所有的介面請求請求的時候,都寫這個日誌。那麼很明顯,我總不能每個介面裡面都加上這個日誌輸出程式碼。無疑是非常繁瑣和重複的。而AOP可以很好的幫助我們去簡化這個冗餘的程式碼。
- 面向切面。如果說正常的業務邏輯是水平的,那麼AOP就是垂直的。可以參考X-Y軸的概念。給每個業務邏輯縱向的擴充套件一些功能,將日誌記錄,效能統計,安全控制,事務處理,異常處理等程式碼從業務邏輯程式碼中劃分出來,而這些功能很明顯是可以公用的。
- 空間意義上的AOP:
在執行正常的業務邏輯時,我們可以利用AOP進行縱向的擴充套件。而不影響它自己的業務邏輯功能。Spring有很多地方都採用了AOP的思想。 - 我的這個專案是判斷使用者的執行許可權。具體業務邏輯:
1)有操作人員進行使用者許可權配置的時候,刪除了某人。往後臺發出刪除使用者的請求。
2)AOP將該請求攔截,判斷該使用者是否有許可權做該操作。如果有,繼續執行接受請求之後的方法,如果沒有,則返回前端json,表示該使用者無許可權操作。
專案開發歷程
說起來還是非常簡單的。現在我開始說一下我的開發歷程。
- Springboot專案中往前端返回特定json字串的相關配置,以及自定義異常的攔截肯定是都要有的。
- 實現對許可權的AOP控制,首先要有一個特殊識別符號,不可能對所有的方法都進行許可權控制,只對特定的方法進行許可權控制。所以我先自定義了一個註解 Permission
一、自定義註解Permission
import java.lang.annotation.*;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/4/18 5:24 PM
* @Desc: 自定義許可權註解,用於AOP
*/
@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
@Documented
public @interface Permission {
}
註解的註解:元註解
1. @TARGET
* 用於標註這個註解放在什麼地方,類上,方法上,構造器上
* ElementType.METHOD 用於描述方法
* ElementType.FIELD 用於描述成員變數,物件,屬性(包括enum例項)
* ElementType.LOCAL_VARIABLE 用於描述區域性變數
* ElementType.CONSTRUCTOR 用於描述構造器
* ElementType.PACKAGE 用於描述包
* ElementType.PARAMETER 用於描述引數
* ElementType.TYPE 用於描述類,介面,包括(包括註解型別)或enum宣告
2.@Retention
* 用於說明這個註解的生命週期
* RetentionPolicy.RUNTIME 始終不會丟棄,執行期也保留該註解。因此可以使用反射機制來讀取該註解資訊。
* 我們自定義的註解通常用這種方式
* RetentionPolicy.CLASS 在類載入的時候丟棄,在位元組碼檔案的處理中有用。註解預設使用這種方式
* RetentionPolicy.SOURCE 在編譯階段丟棄,這些註解在編譯結束後就不再有任何意義,所以他們不會寫入位元組碼中
* @Override,@SuppressWarnings都屬於這類註解。
* 我們自定義使用中一般使用第一種
* java過程為 編譯-載入-執行
3. @Documented
* 將註解資訊新增到文字中
本專案中的Permission註解用於方法,所以我在controller的特定需要許可權控制的方法上新增該註解即可。
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/4/18 6:17 PM
* @Desc: 使用者Controller類
*/
@Slf4j
@RestController
@RequestMapping(value = "/user")
@Api(tags = "UserController")
public class UserController {
@Permission
@ApiOperation( value = "增加使用者", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestMapping(value = "/addUser",method = RequestMethod.POST)
public Response<Void> addUser(@RequestBody AddUserRequestVO vo){
/**
* 具體程式碼實現邏輯省略
*/
return new Response<>();
}
}
我使用了swagger2,用於快速構建RESTFUL API,方便除錯。後續我將會攥寫有關swagger的配置。
4. RequestVO
該專案的許可權控制只需要獲取它的許可權識別符號和操作人員ID,然後呼叫寫好的校驗service去執行校驗方法即可。
於是我定義了一個RequestVO
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/4/18 6:18 PM
* @Desc: 許可權控制的請求vo基類
*/
@Setter
@Getter
@NoArgsConstructor
public class RequestVO {
/**
* 許可權識別符號
*/
private String authrity;
/**
* 操作人員ID
*/
private Long adminId;
}
讓所有的需要許可權控制的介面,請求vo全部繼承這個控制許可權VO基類。
在獲取攔截的方法中的引數之後,直接去呼叫service方法校驗即可。
二、AOP配置
先貼程式碼
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/4/18 6:46 PM
* @Desc:
*/
@Slf4j
@Aspect
@Component
@ResponseBody
public class PermissionAspect {
@Autowired
private CheckService checkService;
@Around( value = "@annotation(permission)")
public Response PermissionCheck(ProceedingJoinPoint joinPoint, Permission permission) throws Throwable{
log.info("======開始許可權校驗======");
//1.獲取請求引數
Object[] objects = joinPoint.getArgs();
for(Object obj : objects){
if (obj instanceof RequestVO){
Long adminId = ((RequestVO) obj).getAdminId();
String authority = ((RequestVO) obj).getAuthrity();
//若校驗失敗,丟擲自定義異常
if (checkService.check( adminId,authority )){
log.info( "=======許可權校驗失敗======" );
throw new BusinessException( "抱歉,您無該操作許可權" );
}
log.info( "=======許可權校驗成功======" );
//若校驗成功,繼續方法的執行,並獲取返回結果,返回給前端
try {
Object object = joinPoint.proceed();
if (object instanceof Response){
return (Response)object;
}
} catch (Throwable throwable) {
/**
* 在方法執行過程中,捕獲異常
* 如果捕獲的是自定義異常,則取出內容並丟擲
* 如果捕獲的不是自定義異常,直接丟擲
*
*/
if(throwable instanceof BusinessException){
throw new BusinessException( throwable.getMessage() );
}
throw new Exception( throwable );
}
}
}
return new Response( );
}
}
這一串程式碼基本上就是我專案中整個AOP的配置了。
- 基本註解
@Aspect : 將當前類標識為一個切面
@Component :肯定是必不可少的。讓Spring容器掃描到。
@ResponseBody 在我的專案中,在後續的許可權校驗後,會返回特定的json物件給前端,所以此處加了該註解。視具體專案而論
- 方法註解
1)@Before 前置通知,在方法執行之前
2)@After 後置通知,在方法執行之後
3)@Around 環繞通知,在方法執行之前執行之後都可以。也是我的程式碼中使用的
裡面的格式
@Around( value = "@annotation(xxx)")即可 xxx為你的自定義註解名
使用該註解,方法引數中第一個引數必須是 ProceedingJoinPoint
4)@Pointcut 定義切點
3.通過 ProceedingJoinPoint獲取方法引數
Object[] getArgs() 獲取方法引數
Signature getSignature() :獲取方法簽名物件; (後跟.getName 即可獲取方法名)
Object getTarget:獲取目標物件
- 獲取引數後,本來是用的
Arrays.asList(objects).stream().forEach( object -> {} );
可是由於其中不能直接方法返回,所以只能用for迴圈迭代陣列。
如果有更好的實現方法,歡迎留言提出。
5. 直接校驗許可權,如果許可權校驗失敗,直接返回自定義異常。如果校驗成功,繼續執行介面中的方法。
Object object = oinPoint.proceed();
該方法是需要加上try…catch的。可是加上去之後,預設catch的異常是
Throwable 。在介面方法具體實現中丟擲的自定義異常,可能就無法被我的異常捕獲器捕獲。
所以先判斷捕獲的異常是否是自定義異常,如果是,就繼續丟擲我的自定義異常。如果不是,則丟擲預設的Exception。
if(throwable instanceof BusinessException){
throw new BusinessException( throwable.getMessage() );
}
throw new Exception( throwable );
Object即是介面方法繼續執行後的返回值。
專案中我封裝了一個Response,專門用於與前端互動。
import org.springframework.http.HttpStatus;
@Setter
@Getter
public class Response<T> {
private String code;
private String message;
private String updateTime;
private T body;
public Response code(String code) {
this.code = code;
return this;
}
public Response body(T body) {
this.body = body;
return this;
}
public Response message(String message) {
this.message = message;
return this;
}
public Response time(String updateTime) {
this.updateTime = updateTime;
return this;
}
/**
* 該構造方法預設code 為200
*/
public Response() {
this(HttpStatus.OK.name(), null);
}
/**
* 該構造方法預設code 為200
* @param body 需要返回的物件
*/
public Response(T body) {
this(HttpStatus.OK.name(), body);
}
public Response(String code, T body) {
this(code, null, body);
}
public Response(String code, String message, T body) {
this.code = code;
this.body = body;
this.message = message;
}
}
我的所有介面返回值都是封裝為Response,我只需要判斷一下object是否是我的Response類,即可直接返回給前端。
Object object = joinPoint.proceed();
if (object instanceof Response){
return (Response)object;
}
以上就是我專案中的AOP實現了。
通過自定義註解的方式,去動態的控制部分介面方法執行AOP許可權控制。
總的來說,收穫還是很大的。
後續:
1. 在研究了aop的註解之後,發現@Around並不適合我的這個許可權校驗。用@Before更加簡單一些,也不需要再去處理方法處理後的情況。
於是修改了一下:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/4/18 6:46 PM
* @Desc:
*/
@Slf4j
@Aspect
@Component
public class PermissionAspect {
@Autowired
private CheckService checkService;
@Before( value = "@annotation(permission)")
public void PermissionCheck(JoinPoint joinPoint, Permission permission){
log.info("======開始許可權校驗======");
//1.獲取請求引數
Object[] objects = joinPoint.getArgs();
for(Object obj : objects){
if (obj instanceof RequestVO){
Long adminId = ((RequestVO) obj).getAdminId();
String authority = ((RequestVO) obj).getAuthrity();
//若校驗失敗,丟擲自定義異常
if (checkService.check( adminId,authority )){
log.info( "=======許可權校驗失敗======" );
throw new BusinessException( "抱歉,您無該操作許可權" );
}
log.info( "=======許可權校驗成功======" );
}
}
}
}
直接用@Before只需要關心方法執行前的許可權校驗即可。後續的請求處理就不需要管了。
- @Pointcut的使用
如果不使用自定義註解的方式去控制哪些方法或類經過你的aop控制,也可以直接定義Pointcut(切點)。
程式碼如下:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.Arrays;
/**
* @Project:
* @Author: Mr_yao
* @Date: 2019/1/28 6:13 PM
* @Desc: AOP實現列印介面呼叫日誌
*/
@Aspect
@Slf4j
@Component
public class LogAspect {
/**
* 定義切點,這是一個標記方法
* com.xxx.xxx.service下的所有子包及方法
*/
@Pointcut("execution( * com.xxx.xxx.service..*.*(..))")
public void anyMethod() {
}
@Before( "anyMethod()" )
public void Before(JoinPoint joinPoint){
log.info( "========接受到請求========" );
}
@AfterReturning("anyMethod()")
public void afterMethod(){
log.info( "=======請求處理完畢========" );
}
@AfterThrowing("anyMethod()")
public void afterThrowMethod(){
log.info( "=======請求處理異常========" );
}
}
定義某一個地方為切點,裡面的語法可以自己網上搜尋,可以直接標識到某個包及包下的所有子類。只在你的切點範圍內,會執行AOP對應操作。也是很方便的
這個時候 @Before @After中的註解範圍就是你的切點方法了
@After 方法執行完後通知(不論是執行成功還是異常)
@AfterReturning 方法正常執行後通知
@AfterThrowing 方法丟擲異常後通知
不管是用切點還是自定義註解的方式,都可以控制AOP執行的範圍。視專案而定即可。
ProceedingJoinPoint extends JoinPoint
具體差異大家可以看原始碼。
第一次寫部落格,各位大牛多多包涵哈。歡迎留言評論?
相關文章
- spring aop實現許可權控制,路徑控制Spring
- spring aop實現簡單的許可權控制功能Spring
- 什麼是AOP系列之二:AOP與許可權控制實現(轉)
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- 許可權控制及AOP日誌
- spring aop實現許可權管理Spring
- Laravel實現許可權控制Laravel
- 分享!! 如何自定義許可權校驗的註解並用AOP攔截實現許可權校驗
- 提問:使用spring aop實現許可權管理Spring
- 前端許可權控制系統的實現思路前端
- springboot + shiro 實現登入認證和許可權控制Spring Boot
- 【轉】一個關於用AOP實現許可權控制的問題,不知道大家怎麼想?
- Vue 前端應用實現RBAC許可權控制的一種方式Vue前端
- Spring Security實現統一登入與許可權控制Spring
- springboot-許可權控制shiro(二)Spring Boot
- Nestjs RBAC 許可權控制管理實踐(一)JS
- Elasticsearch 許可權控制Elasticsearch
- HIVE的許可權控制和超級管理員的實現Hive
- SpringMVC使用攔截器實現許可權控制SpringMVC
- 舉例如何控制查詢許可權
- 一對一原始碼,前端頁面許可權和按鈕許可權控制原始碼前端
- Linux的許可權控制Linux
- Atlas 2.1.0 實踐(4)—— 許可權控制
- 需要一個前臺許可權管理,或問如何實現
- Linux許可權控制Linux
- Appfuse:許可權控制APP
- SpringBoot--- SpringSecurity進行登出,許可權控制Spring BootGse
- SpringBoot整合SpringSecurityOauth2實現鑑權-動態許可權Spring BootGseOAuth
- Django許可權機制的實現Django
- Springboot+Vue實現線上聊天室專案-整合springSecurity配置實現登入的許可權控制Spring BootVueGse
- 資料分析的許可權控制
- Solaris下控制ftp的許可權FTP
- 如何優雅的在 vue 中新增許可權控制Vue
- Vue | 自定義指令和動態路由實現許可權控制Vue路由
- springcloud-gateway整合jwt+jcasbin實現許可權控制SpringGCCloudGatewayJWT
- [WCF許可權控制]透過擴充套件自行實現服務授權套件
- mysql 許可權控制筆記MySql筆記
- oracle列級許可權控制Oracle