一、缘由
在我们日常系统的开发中,不可避免的需要对外提供一些接口,比如公司多个系统需要调用我们负责的系统中某个接口、功能。
而这类接口往往涉及到公司的数据,对于安全性需要一定的保证,总不能写一个接口,所有的调用都响应吧?
所以就有了此文分享。
二、方案
任何技术和方案都具有多面性,有利也有弊,我们只能根据自己的实际业务作出取舍。
1.基于token等机制做统一权限校验
市面上开源权限框架有很多,如Shiro、Spring Security、Sa-Token...
利:安全性大大提高,使用场景适应强,内外网都可开放。
弊:学习、时间成本、耦合成本,调用方对接成本。
适合:涉及系统核心业务、数据接口,这类往往对安全性有要求,可以忽略弊端,公司能够提供。
2.直接放行指定接口
利:快、省时间。(如果是内网调用,这是最快的,也不必太担心安全问题)
弊:外网,第三方调用(一旦接口暴露=数据裸奔)
适合:内部系统使用,非核心系统,没有太多有用的数据接口。
3.约定调用请求携带密钥
利:快、省时间,可重复使用。
弊:密钥泄露后容易造成泄露事故(一旦接口暴露=数据裸奔)
适合:内部系统使用,不涉及重大业务流程和数据。
...其实方案远远不止这三种,这里不做过多的分享。
三、分享
近期,我们这边引入了统一的作业调度平台(XXL-JOB),许多系统之前都是各自为战,分别的开发写进代码进行控制作业的调度。现在引入了统一的平台,进行管理。为了方便XXL-JOB进行调用,我们需要把接口进行开放,同时,为了适应xxl-job调用,我们不能设置权限,需要放行,但是一旦放行,那接口地址暴露,其他人也能进行调用,安全性太差。
所以,经过内部讨论,我采用了上面第三种方案进行解决。
采用AOP,自定义注解进行处理,方便调用,无需重复编码。
1.自定义注解类@XxlJobAuth
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface XxlJobAuth {
/**
* 是否启用
*
* @return boolean
*/
boolean enabled() default true;
}
2.配置切面,进行参数校验和验证
@Slf4j
@Aspect
@Component
public class XxlJobAuthAspect {
//yml文件中写入约定密钥
@Value("${xxl-job.odAppSecret}")
private String odAppSecret;
@Around("@annotation(自定义注解目录.auth.annotation.XxlJobAuth)")
public Object invoke(ProceedingJoinPoint point) throws Throwable {
//获取方法参数值数组
Object[] args = point.getArgs();
//获取请求信息
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
Map<String, String[]> parameterMap = request.getParameterMap();
Map<String, String> paramMap = new HashMap<>(8);
parameterMap.forEach((key, value) -> paramMap.put(key, String.join(",", value)));
log.info("=====xxl-job调用接口权限验证=====");
log.info("类型:{}", request.getMethod());
log.info("Url:{}", request.getRequestURI());
log.info("路径:{}", point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
log.info("参数:{}", JSONUtil.toJsonStr(paramMap));
log.info("===============end==============");
//未携带指定参数key
if (!paramMap.containsKey("odAppKey") || !paramMap.containsKey("odSign") || !paramMap.containsKey("odUtime")) {
return Result.fail(ResultCode.CONFIG_ID_IS_NOT_EXIST, "密钥缺失,禁止访问");
}
//获取的参数名称对应的值加密进行对比
JSONObject json = JSONUtil.parseObj(paramMap);
String odsign = SecureUtil.md5(json.getStr("odAppKey") + json.getStr("odUtime") + odAppSecret);
if (!odsign.equalsIgnoreCase(json.getStr("odSign"))) {
return Result.fail(ResultCode.CONFIG_IS_NOT_EXIST, "密钥无效,禁止访问");
}
return point.proceed(args);
}
}
3.使用,在需要对外提供的接口上加上@XxlJobAuth注解即可。
@XxlJobAuth
@GetMapping("/test")
public Result<?> test(@RequestParam(value = "statusType", defaultValue = "3") int statusType, @RequestParam(value = "beginTime", required = false)
Long beginTime, @RequestParam(value = "endTime", required = false) Long endTime) {
return iOrderTaskProvider.test(statusType, beginTime, endTime);
}
注意:如果我们框架有配置统一的安全权限校验,记得对指定接口进行放行,避免第一层就被拦截了。
四、结
以上就是本次的记录了,虽然有使用AOP,但是AOP绝不仅仅只能如此,更多的使用和需求,还需要自己去研究和论证。