一、缘由

        在我们日常系统的开发中,不可避免的需要对外提供一些接口,比如公司多个系统需要调用我们负责的系统中某个接口、功能。

而这类接口往往涉及到公司的数据,对于安全性需要一定的保证,总不能写一个接口,所有的调用都响应吧?

所以就有了此文分享。

二、方案

任何技术和方案都具有多面性,有利也有弊,我们只能根据自己的实际业务作出取舍。

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);
    }

注意:如果我们框架有配置统一的安全权限校验,记得对指定接口进行放行,避免第一层就被拦截了。

android room 打印 日志 aop打印日志对性能影响_spring

 四、结

以上就是本次的记录了,虽然有使用AOP,但是AOP绝不仅仅只能如此,更多的使用和需求,还需要自己去研究和论证。