前言
有时候项目中需要对接口进行校验,增加鉴权,确保 API 不被恶意调用。
项目中都是这样
这样,部分需要查询一些信息,下文需要使用
这样的代码很多,重复率太高。看着我蛋疼,对此优化一下。
方案
1 传统做法每个控制层加 if 判断
if (!distributorService.validToken(tokenDto)) {
return new Result(false, ResultCode.UNAUTHORIZED.val(), ResultCode.UNAUTHORIZED.msg());
}
这样每个控制层都需要增加代码,代码重复量很多。
2 使用过滤器进行拦截校验,部分接口不需要校验可以设置白名单等注解跳过
这样看着也可以,但对特定场景可能不太使用,一些模块需要校验,一些需要都查询数据。
或者给用户分配不同的角色,然后不同的角色对某一个方法有不同的权限,有些角色可以访问该方法,有的不能访问。这时候我们可以利用aop实现权限验证。
3 使用 Aop进行统一权限验证
实现方式呢,既然使用aop了,aop可以对注解进行代理。
控制层
这里或者可能根据平台id去查平台下的一些信息,部分接口会用 部分接口不会用,但所有的接口都做校验,一些接口需要根据查平台下的一些信息。
那我们如何实现呢?
- 业务里if大法好
- 使用aop优雅的实现
@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 就方便优雅很多。
具体做法
实现思路
就是首先自定义一个注解,在方法上添加该注解,跟据注解的值来判断能否访问该方法。
定义注解,不同校验类型,校验类型,校验和查询类型
枚举类
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;
}
//省略.....
}
定义注解
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 "";
}
校验实体
@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 类
根据注解类型对其进行权限验证
或者权限验证+查询数据
,当然如果还有其他的类型,可以继续扩展。
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较为优雅。
业务中根据具体场景去使用。