采用自定义注解判定第三方应用接口调用的权限问题
背景:应用和第三方对接的时候,会暴露某些rest 接口给第三方使用,这个时候我们通常需要校验三方应用的合法行,常用做法就是要求应用在header里携带我们颁发的合法的appId,appKey,据此进行相关判定,此处如何判定,下次另外写一篇博客记录。同时,我们规定,不同的appId只能访问一定范围的接口。
核心需求:不同的appId可以动态可配置不同的接口;
逻辑梳理:系统启动时,加载所有的对外接口保存在内存或数据库中,当配置时,即界面为某个应用分配一个appId时,从后台获取所有可配置的接口,界面以表格形式展示出来,供管理员勾选,选中的接口数据保存在对应的表中,即此appId关联了哪些rest接口。当第三方应用访问时,首先判定appId和appKey的合法性,再加载此appId可访问的rest接口结合,如果当前访问的url符合要求,则放行,否则提示无权限。
实现方式:本文主要讲解如何加载所有的对外接口。
- 将需要暴露出去的接口信息,统一写在一个配置文件中,系统启动时,利用文件流可加载此文件,按行读取,解析。
- 在需要暴露的方法上加上自定义注解,系统启动时根据此注解来解析。此法牵涉知识点较多,故详细讲解。
逻辑实现已经叙述的较为详细了,下面讲述几个重要环节。
- 注解的定义,具体属性可根据业务需求制定。下面是我写的几个属性。
/**
* 对外暴露的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. 注解的使用
@RestControllerpublic 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初始化或者刷新的时候会发出此通知事件。代码如下:
@Componentpublic 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()方法
@Componentpublic 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