前言
有時候項目中需要對接口進行校驗,增加鑒權,確定 API 不被惡意調用。
項目中都是這樣
這樣,部分需要查詢一些資訊,下文需要使用
這樣的代碼很多,重複率太高。看着我蛋疼,對此優化一下。
方案
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