1. 外部从Gateway访问,需要鉴权(eg.CURD操作)。这种是最常使⽤的,⽤户登录后正常访问接⼝,不需要我们做什么处理(可能有的接⼝需要加权限字段)。
2. 外部从Gateway访问,不需要鉴权(eg.短信验证码)。需要我们将uri加⼊到security.oauth2.client.ignore-urls配置中,可以不需要鉴权访问
3. 内部服务间⽤Feign访问,不需要鉴权(eg.Auth查询⽤户信息)。也是需要我们将uri加⼊到security.oauth2.client.ignore-urls配置中,那与第⼆种的区别就是这种情况下⼤多数都是服务可以请求另⼀个服务的所有数据,不受约束,那我们如果仅仅只配置ignore-url的话,外部所有⼈都可以通过url请求到我们内部的链接,安全达不到保障。
鉴于上述第三种情况,配置了ignore-url和Feign,此时该接⼝不需要鉴权,服务内部通过Feign访问,服务外部通过url也可以访问,所以Pigx中,加⼊了⼀种@RequestHeader(SecurityConstants.FROM)的处理⽅式。即在接⼝⽅法中,对头部进⾏判断,只有请求带上相应的Header参数时,才允许通过访问,否则抛出异常。那这时候其实我们在外⽹通过Gateway访问的时候,也可以⼿动带上这个Header参数,来达到这个⽬的。所以我们便在Gateway中设置了⼀个GlobalFilter过滤器:
这个过滤器在处理HttpRequest的时候,会删除从外部请求头⾥的SecurityConstants.FROM这个参数。此时的效果就是,这个URL从外部访问不需要鉴权,但由于Gateway的过滤,最终到达我们接⼝⽅法时,由于缺少头部信息,被拒绝访问;⽽服务间通过Feign访问,不经过Gateway,则可以正常访问。
那原始的处理⽅法和处理逻辑就是这样:⾸先将uri加⼊ingore-url,然后在接⼝的⽅法和Feign的接⼝参数中写上RequestHeader参数,最后在Feign-Client中带上这个SecurityConstants.FROM参数。既然这种逻辑都是相同的,那后⾯的pigx版本发⾏后,就使⽤AOP将此步骤抽离出来,成为了Inner。
Inner的处理流程
统⼀的ignore-url处理
⾸先我们来看看这个注解的代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
/**
* 是否AOP统一处理
*
* @return false, true
*/
boolean value() default true;
/**
* 需要特殊判空的字段(预留)
*
* @return {}
*/
String[] field() default {};
}
⾸先,在我们项⽬加载阶段,我们获取有Inner注解的类和⽅法,然后获取我们配置的uri,经过正则替换后⾯的可变参数为*,然后将此uri加⼊到ignore-url中。此时我们就能达到所有Inner配置的⽅法/类上的接⼝地址,都统⼀在项⽬加载阶段⾃动帮我们加到ignore-url中,不需要我们⼿动配置,免去了很多开发⼯作,同时也能避免我们忘记配置,⽽浪费开发时间。核⼼代码如下:
@Slf4j
@Configuration
@ConditionalOnExpression("!'${security.oauth2.client.ignore-urls}'.isEmpty()")
@ConfigurationProperties(prefix = "security.oauth2.client")
public class PermitAllUrlProperties implements InitializingBean {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
@Autowired
private WebApplicationContext applicationContext;
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method)
.ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, StringPool.ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
Optional.ofNullable(controller)
.ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, StringPool.ASTERISK))));
});
}
}
统一的安全处理
那上边讲到的,如果我们不希望这个url可以直接被外部调用,仅能在feign功能中使用,该怎么处理?
我们使⽤⼀个Spring-AOP,在对所有Inner注解的⽅法做⼀个环绕增强的切点,进⾏统⼀的处理。在上⾯我们提到的Inner的value参数,当该参数为true时,我们对⽅法的⼊参进⾏判断,仅当符合我们定制的⼊参规则时(Pigx这⾥是⽤的@RequestHeader(SecurityConstants.FROM) 与SecurityConstants.FROM_IN做⽐较),我们对它进⾏放⾏,不符合时,抛出异常;当value为false时,咱不做任何处理,此时Inner仅起到了⼀个ignore-url的作⽤
通过这两步呢,我们⾸先是在加载时通过找到Inner注解,将相应的uri加⼊到ignore-url中,达到⾃动化配置的⽬的;之后我们⼜使⽤切⾯对Inner的⽅法进⾏环绕处理,达到安全控制。对⽐之前的处理⽅式,现在我们使⽤⼀个@Inner注解,就能很快的满⾜上⾯说的两种场景,⼤⼤节省了我们的开发时间。
合理的使⽤@Inner注解
上⾯提到的两种应⽤场景,在我们的代码中,其实都是可以使⽤Inner注解的,下⾯结合Feign做⼀个简单的⽰例,⽰例场景就是我们的获取操作日志然后保存数据:
1. 在接⼝上使⽤@Inner注解,使得url⽆需鉴权
2. 编写Feign接⼝
3.Feign-Client中调⽤接⼝,带上SecurityConstants.FROM_IN参数为内部识别