如何優雅的使用切面和註解實現權限驗證

背景

權限驗證在咱們系統中是一個與業務邏輯無關可是又與業務息息相關的一個功能。
設想咱們開發了一款爲中小型企業定製的會員系統。這款系統能夠爲企業A、企業B等多種企業提供服務。數據庫中的表結構每每是這樣的(如下只是一個demo,實際狀況中字段必定會更多、更復雜):java

id memberCardCode userName card_status business
1 a564456578 zhangsan 0 business-a
2 b678688643 lisi 1 businsss-b
3 a775445667 wangwu 0 businsss-a
4 b943578978 zhaoliu 1 businsss-b
5 c657688799 sunqi 1 businsss-c

基於上表,咱們刪除id = 1的會員每每是這樣操做的(假設是物理刪除):
controller層:
sql

@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    public void deleteById(int id) {
        // 此處省略刪除代碼
    }
}
複製代碼

最終在controller中調用的SQL語句是這樣的:數據庫

delete from member where id = 1;
複製代碼

乍一看,就這樣一條簡單sql語句能有什麼問題呢?其實越是簡單的問題,越不能放過。
經過上表咱們看到id = 1的會員信息是屬於business-a的。因此理應是business-a的帳號才能刪除id = 1的會員信息。那此時若是business-b在刪除會員的時候將參數id改成1,此時就會出現business-b刪除了business-a的會員。此時business-a的心情是崩潰的。
app

因此權限驗證是很是必要。那權限驗證怎麼作呢?

不太優雅:侵入業務代碼的方案

在controller層加入邏輯判斷:判斷刪除的id是否屬於當前帳號,若是屬於則刪除;不然直接返回。代碼以下:spa

@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    public void deleteById(int id) {
        // 第一步:權限驗證
        Integer id = selectByIdAndBusiness(id, "business-a");
        if (id == null) {
            // 說明id不屬於business-a不能刪除
            return;
        }
        // 第二步:調用刪除邏輯
    }
}
複製代碼

上面的代碼確實解決了越權的問題,可是會將一些業務無關的代碼侵入到咱們的業務邏輯,這樣的實現邏輯不太優雅。那咱們來選擇一個更優雅的方式來完成權限驗證。code

優雅:註解 + 切面的方案

首先咱們須要明確關注的字段信息:id和business。其中business能夠在filter中存入一個ThreadLocal,因此咱們只須要關注字段id便可。
第一步:建立自定義註解(做用於方法)cdn

@Documented  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface Auth {
    // 方法的參數名稱,以防參數名稱不是id,因此提供paramName
    String paramName() default "id";
}
複製代碼

第二步:在方法上使用註解blog

package com.demo.controller;
@RestController("/member")
public class MemberController {
    @PostMapping("/delete")
    @Auth(paramId = "deleteId")
    public void deleteById(int deleteId) {
        // 調用刪除邏輯
    }
}
複製代碼

第三步:實現切面開發

// 經過註解能夠看到,咱們該方法切的是controller層帶有Auth註解的方法
@Before(value = "execution(public * com.demo.controller..*.*(..))"
      + " && @annotation(auth)", argNames = "pjp, auth")
public void before4Auth(JoinPoint pjp, Auth auth) {
    // 一、經過ThreadLocal獲取business
    String business = context.get();
    // 二、經過註解解析id
    // 2.1 獲取參數值
    Object[] args = pjp.getArgs();
    // 2.2 獲取參數名
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    String[] parameterNames = methodSignature.getParameterNames();
    // 2.3 獲取註解中paramName的下標
    int index = ArrayUtils.indexOf(parameterNames, auth.paramName());
    // 2.4 根據下標獲取id對應的值
    int val = (int) args[index];
    // 2.5 鑑權邏輯
    Integer id = selectByIdAndBusiness(val, business);
    if (id == null) {
        // 拋異常提示越權
    }
    // 不然的正常執行下面的業務邏輯
}
複製代碼

經過這種方式,咱們只需在controller層的方法加上@Auth註解便可。沒有非業務代碼的侵入,實現方式可算優雅。
若是您有更優雅的解決方案,歡迎提供思路。get

相關文章
相關標籤/搜索