采用自定义注解判定第三方应用接口调用的权限问题

编程

背景:应用和第三方对接的时候,会暴露某些rest 接口给第三方使用,这个时候我们通常需要校验三方应用的合法行,常用做法就是要求应用在header里携带我们颁发的合法的appId,appKey,据此进行相关判定,此处如何判定,下次另外写一篇博客记录。同时,我们规定,不同的appId只能访问一定范围的接口。

 

核心需求:不同的appId可以动态可配置不同的接口;

 

逻辑梳理:系统启动时,加载所有的对外接口保存在内存或数据库中,当配置时,即界面为某个应用分配一个appId时,从后台获取所有可配置的接口,界面以表格形式展示出来,供管理员勾选,选中的接口数据保存在对应的表中,即此appId关联了哪些rest接口。当第三方应用访问时,首先判定appId和appKey的合法性,再加载此appId可访问的rest接口结合,如果当前访问的url符合要求,则放行,否则提示无权限。

 

实现方式:本文主要讲解如何加载所有的对外接口。

  1. 将需要暴露出去的接口信息,统一写在一个配置文件中,系统启动时,利用文件流可加载此文件,按行读取,解析。
  2. 在需要暴露的方法上加上自定义注解,系统启动时根据此注解来解析。此法牵涉知识点较多,故详细讲解。

 

逻辑实现已经叙述的较为详细了,下面讲述几个重要环节。

  1. 注解的定义,具体属性可根据业务需求制定。下面是我写的几个属性。
    /**

    * 对外暴露的urls注解

    */

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    public @interface ExposuresUrl {

    /**

    * 具体的url

    * @return

    */

    String value() default "";

    /**

    * 此url的描述信息

    * @return

    */

    String description() default "";

    /**

    * url的请求方式

    * @return

    */

    HttpMethod method() default HttpMethod.GET;

    /**

    * 暴露的模块,USER,OU,ENTIELEMENTS,SIGNATURE

    * @return

    */

    String module() default "OTHERS";

    /**

    * 排序大小

    * @return

    */

    int order() default 0;

    }

 

2. 注解的使用

@RestController

public class OuController {

@ExposuresUrl(value = "/api/v1/app/organizations/{id}",description = "获取组织机构详情")

@GetMapping("/{id}")

public ResponseEntity<?> getDetail(

@PathVariable String id, HttpServletRequest request){//逻辑处理}

}

 

3. 如何加载自定义的注解。此处介绍两种可行的方式,供大家选择。

(1)实现 ApplicationListener<ContextRefreshedEvent> 接口

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

/**

* Handle an application event.

* @param event the event to respond to

*/

void onApplicationEvent(E event);

}

此接口是基于java.util.EventListener的标准接口,采用观察者模式。而通常我们注入 ContextRefreshedEvent 事件即可,也就是ApplciationContext初始化或者刷新的时候会发出此通知事件。代码如下:

@Component

public class ExposureUrlsListener implements ApplicationListener<ContextRefreshedEvent> {

private static Map<String,String> urls= Maps.newHashMap();

@Override

public void onApplicationEvent(ContextRefreshedEvent event) {

Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(RestController.class);

for (Object bean : beans.values()) {

Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());

for(Method method:methods){

ExposuresUrl annotation = method.getAnnotation(ExposuresUrl.class);

if (annotation!=null){

urls.put(annotation.description(),annotation.value()+"("+annotation.method().name()+")");

}

}

}

}

public Map<String,String> getUrls(){

return urls;

}

}

几个重要注意点

ExposuresUrl 注解只能用在方法上,且是在RestController注解类下的方法。具体使用见后文。

event.getApplicationContext().getBeansWithAnnotation(Class clazz) 方法获取某个注解的前提条件是,此类必须被spring管理起来了,且只能获取到bean类上的注解。要是直接获取自定义注解ExposuresUrl,而ExposuresUrl注解在方法上,这当然是获取不到的。

进一步,如是ExposuresUrl也可以注解在类上,能否获取到呢?如果类上只有这一个注解也是不能获取到的,因为没有被spring托管。如果加上了@Component等spring扫描了的注解,则可行。

所以,网上由些博客真是坑人,全是抄抄抄,完全没有具体场景注意事项的说明。

 

(2)实现BeanPostProcessor,重写postProcessAfterInitialization()方法

@Component

public class ScanExposureUrlsProcess implements BeanPostProcessor{

@Override

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

// 所有的bean都会调用

Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());

if (methods != null) {

for (Method method : methods) {

ExposuresUrl annotation = method.getAnnotation(ExposuresUrl.class);

if (annotation!=null){

System.out.println(annotation.description()+"========="+annotation.value()+"("+annotation.method().name()+")");

}

}

}

return bean;

}

}

这两种方式都可以实现我们的需求,但是前提是bean都要被spring管理,这个重点。二者孰优孰劣呢?

初步说来,第一种方式似乎目的更加明确,能够快速缩小bean的筛选范围,而第二种方式有点大海捞针的感觉,所有的bean初始化后都会调用此方法,较为宽泛,更适合一种通用的逻辑处理。所以推荐第一种使用方式。

 

 

以上是 采用自定义注解判定第三方应用接口调用的权限问题 的全部内容, 来源链接: utcz.com/z/515625.html

回到顶部