天天看點

授權驗證方式有很多、但AOP最為優雅。

作者:老馬帶你學程式設計

前言

有時候項目中需要對接口進行校驗,增加鑒權,確定 API 不被惡意調用。

項目中都是這樣

授權驗證方式有很多、但AOP最為優雅。

這樣,部分需要查詢一些資訊,下文需要使用

授權驗證方式有很多、但AOP最為優雅。

這樣的代碼很多,重複率太高。看着我蛋疼,對此優化一下。

方案

1 傳統做法每個控制層加 if 判斷

java複制代碼   if (!distributorService.validToken(tokenDto)) {
            return new Result(false, ResultCode.UNAUTHORIZED.val(), ResultCode.UNAUTHORIZED.msg());
     }
           

這樣每個控制層都需要增加代碼,代碼重複量很多。

2 使用過濾器進行攔截校驗,部分接口不需要校驗可以設定白名單等注解跳過

這樣看着也可以,但對特定場景可能不太使用,一些子產品需要校驗,一些需要都查詢資料。

或者給使用者配置設定不同的角色,然後不同的角色對某一個方法有不同的權限,有些角色可以通路該方法,有的不能通路。這時候我們可以利用aop實作權限驗證。

3 使用 Aop進行統一權限驗證

實作方式呢,既然使用aop了,aop可以對注解進行代理。

控制層

這裡或者可能根據平台id去查平台下的一些資訊,部分接口會用 部分接口不會用,但所有的接口都做校驗,一些接口需要根據查平台下的一些資訊。

那我們如何實作呢?

  • 業務裡if大法好
  • 使用aop優雅的實作
java複制代碼

 @DBLoadBalance
    @ApiOperation(value = "根據出發城市擷取目的城市", notes = "根據出發城市擷取目的城市")
    @PostMapping(value = "/endCity")
    @Pog(module = Constants.ModuleName.Distribution,type = PlatTypeEnum.validTokenFindPid,description = "根據出發城市擷取目的城市")
    public Result getEndCity(@RequestBody(required = false) PlatformTokenDto<DistributorCitySearchRequest> tokenDto) {
   

//校驗token 業務邏輯
 if (!distributorService.validToken(tokenDto)) {
           throw  new BaseException( ResultCode.UNAUTHORIZED.val(), ResultCode.UNAUTHORIZED.msg());
  }

// 查詢平台id 下資訊 servie需要使用,這裡隻放在這裡說明用
 List<PlatformDistributeAccount> byDistributeId = platformDistributeAccountDao.findByDistributeId(pid);
        if (CollectionUtils.isEmpty(byDistributeId)) {
            log.infoLog(String.format(" 沒有查到 平台id : %s  對應 資訊 ", pid));
            throw new BaseException(ResultCode.BAD_REQUEST.val(), "錯誤配置,請校驗後再請求!");
        }



        return distributorXXXXXXXXXService.getxxxxxxxxEndCity(tokenDto);
    }



 @ApiOperation(value = "平台建立分銷商訂單", notes = "平台建立分銷商訂單")
    @PostMapping(value = "/create")
    @Pog(module = Constants.ModuleName.Distribution,type = PlatTypeEnum.validToken,description = "平台建立分銷商訂單")
    public Result createOrder(@RequestBody PlatformTokenDto<DistributorOrderCreateRequest> tokenDto) {
        loggerHelper.infoLog("參數 -> " + JSON.toJSONString(tokenDto));

//校驗token 業務邏輯,創單不需要查平台id下資訊,會直接根據之前接查結果,傳過來,值校驗token
 if (!distributorService.validToken(tokenDto)) {
           throw  new BaseException( ResultCode.UNAUTHORIZED.val(), ResultCode.UNAUTHORIZED.msg());
  }

}
           

以上這樣的接口很多,傳統方法我們每個接口都加if加判斷 ,以及查詢一些資訊。

但使用 AOP 就友善優雅很多。

具體做法

實作思路就是首先自定義一個注解,在方法上添加該注解,跟據注解的值來判斷能否通路該方法。

定義注解,不同校驗類型,校驗類型,校驗和查詢類型

枚舉類

java複制代碼
public enum PlatTypeEnum {
    /**
     *
     */
    valid_Token(0, "校驗"),
    valid_Token_FIND_PID(1, "查詢 + 校驗"),

    ;

    private int value;

    private String desc;

    public static final int validToken = 0;

    public static final int validTokenFindPid = 1;

    PlatTypeEnum(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
 //省略.....
}
           

定義注解

java複制代碼package com.xxxxxxxxxxx;


import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Pog {

    /**
     * 子產品
     */
    String module() default "";

    /**
     * 類型
     * @return
     */
    int type() default 0;

    /**
     * 描述
     */
    String description() default "";

}
           

校驗實體

java複制代碼@ApiModel(value = "平台分銷商TokenDto", description = "平台分銷商TokenDto")
public class PlatformTokenDto<T> {

    @ApiModelProperty("請求平台id")
    private String pid;

    @ApiModelProperty("請求時間")
    private long timestamp;

    @ApiModelProperty("請求加密串")
    private String token;

    @ApiModelProperty("請求資料")
    private T data;

}
           

AOP 類

根據注解類型對其進行權限驗證或者權限驗證+查詢資料,當然如果還有其他的類型,可以繼續擴充。

java複制代碼package com.xxxxxx.config;

import com.xxxxxx.enums.PlatTypeEnum;
import com.xxxxxx.constant.ResultCode;
import com.xxxxxxx.exception.BaseException;
import com.xxxxx.helper.LoggerHelper;
import com.xxxxx.distributor.dto.PlatformTokenDto;
import com.xxxx.distributor.service.DistributorService;
import com.xxxxxxx.platformdistribute.annotations.Pog;
import com.xxxxxxxxx.platformdistributeaccount.dao.PlatformDistributeAccountDao;
import com.xxxxxxxx.entity.PlatformDistributeAccount;
import com.xxxxxxxxxxx.Constants;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;


@Aspect
@Component
public class PogAspect {


    /**
     * 校驗切入點
     */
    @Pointcut("@annotation(com.xxxxxxxxx.platformdistribute.annotations.Pog)")
    public void logPointCut() {
    }

    @Autowired
    private PlatformDistributeAccountDao platformDistributeAccountDao;

    @Autowired
    private DistributorService distributorService;

    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) {

        //ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        //HttpServletRequest request = attributes.getRequest();

        Object[] args = joinPoint.getArgs();

//這裡擷取參數實體,直接強轉類型,下文根據注解查出 可以設定值
        PlatformTokenDto tokenDto = (PlatformTokenDto)(args[0]);

//這裡用一授權判斷,因為所有接口都需要校驗,當然不需要校驗可以再根據下文注解類型進行判斷。
        if (!distributorService.validToken(tokenDto)) {
           throw  new BaseException( ResultCode.UNAUTHORIZED.val(), ResultCode.UNAUTHORIZED.msg());
        }

        doSetPlatformDistributeAccounts(joinPoint,tokenDto);


    }


    //@AfterReturning(returning = "ret", pointcut = "logPointCut()")
    public void doAfterReturning(Object ret) {
    }

    //@AfterReturning(pointcut = "logPointCut()")
    public void doAfter(JoinPoint joinPoint) {
    }


    private void doSetPlatformDistributeAccounts(JoinPoint joinPoint, PlatformTokenDto tokenDto) {
        String methodName = joinPoint.getSignature().getName();
        Method method = currentMethod(joinPoint, methodName);
        Pog pog = method.getAnnotation(Pog.class);
        if (pog == null) {
            return;
        }

        log.infoLog(String.format(" pog: %s ", pog));

        if (pog.type() != PlatTypeEnum.valid_Token_FIND_PID.getValue()) {
            return;
        }

        String pid = tokenDto.getPid();

//查詢資訊
        List<PlatformDistributeAccount> byDistributeId = platformDistributeAccountDao.findByDistributeId(pid);
        if (CollectionUtils.isEmpty(byDistributeId)) {
            log.infoLog(String.format(" 沒有查到 平台id : %s  對應 vcode ", pid));
            throw new BaseException(ResultCode.BAD_REQUEST.val(), "錯誤配置,請校驗後再請求!");
        }

	//設定值
	//這裡也可以使用 ThreadLocal 進行下文拿值(用完記得remove),看業務需要決定。
	//我這裡直接是放在token dto裡了
        tokenDto.setPlatformDistributeAccounts(byDistributeId);

    }


    @AfterThrowing(value = "logPointCut()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {

        log.infoLog(throwable.getMessage());

    }

    /**
     * 擷取目前執行的方法
     *
     * @param joinPoint  連接配接點
     * @param methodName 方法名稱
     * @return 方法
     */
    private Method currentMethod(JoinPoint joinPoint, String methodName) {
        /**
         * 擷取目标類的所有方法,找到目前要執行的方法
         */
        Method[] methods = joinPoint.getTarget().getClass().getMethods();
        Method resultMethod = null;
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                resultMethod = method;
                break;
            }
        }
        return resultMethod;
    }

}
           

具體那種校驗權限根據業務去使用,上面實作的方法很多,但考慮代碼重複和優雅還是aop較為優雅。

業務中根據具體場景去使用。

作者:tizzybepeacejoy

連結:https://juejin.cn/post/7231804369522671676

繼續閱讀