合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
[TOC] #### @Inner 注解原理和使用 ![](https://img.kancloud.cn/bb/d7/bbd74c79340e0830b8d5bbbd4c6f0cf5_739x298.png) #### ① 服务交互三种情况 * 1. 外部从 Gateway 访问,需要鉴权(eg.CURD 操作)。这种是最常使用的,用户登录后正常访问接口,**不需要添加 @Inner** * 2. 如上图 ④⑤ ,外部从 Gateway 访问不需要鉴权(eg.获取短信验证码接口)。 **只需要再目标接口增加 @Inner(false)** ![](https://img.kancloud.cn/e8/e0/e8e04ad28fc1c510bf35912c97e499a8_1946x492.png) * 3. 如上图 ①②③,外部从 Gateway 访问的请求没有携带 token(eg.登录接口), auth 需要调用 upms 的接口获取用户信息 ![](https://img.kancloud.cn/55/d9/55d9e478c2a38d91d607de63b46f01e2_2274x1326.png) #### ② Inner 的原理 ##### 统一的 ignore-url 处理 首先我们来看看这个注解的代码 ~~~ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Inner {    /**     * 是否AOP统一处理(可以理解为是否仅允许Feign之间调用)     *     * @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 时,我们对方法的入参进行判断,仅当符合我们定制的入参规则时( 这里是用的`@RequestHeader(SecurityConstants.FROM)` 与`SecurityConstants.FROM_IN`做比较),我们对它进行放行,不符合时,抛出异常;当 value 为 false 时,咱不做任何处理,此时 Inner 仅起到了一个 ignore-url 的作用。 ~~~ @Slf4j @Aspect @Component @AllArgsConstructor public class ScaSecurityInnerAspect {    private final HttpServletRequest request;    @SneakyThrows    @Around("@annotation(inner)")    public Object around(ProceedingJoinPoint point, Inner inner) {        String header = request.getHeader(SecurityConstants.FROM);        if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) {            log.warn("访问接口 {} 没有权限", point.getSignature().getName());            throw new AccessDeniedException("Access is denied");       }        return point.proceed();   } } ~~~ 通过这两步呢,我们首先是在加载时通过找到 Inner 注解,将相应的 uri 加入到 ignore-url 中,达到自动化配置的目的;之后我们又使用切面对 Inner 的方法进行环绕处理,达到安全控制。对比之前的处理方式,现在我们使用一个`@Inner`注解,就能很快的满足上面说的两种场景,大大节省了我们的开发时间。 #### ③ 合理的使用@Inner 注解 如下接口通过 Inner 接口不鉴权,Restful pathVariable 风格的请求 ~~~ @Inner @GetMapping("/info/{username}") ~~~ 根据上文原理说明 ,会给 spring security 添加一条 `/info/*` 的 忽略拦截规则,这样会导致只要是满足此规则的路径都会进行 token 校验,获取不到用户信息。 (这也就是为啥 SecurityUtils.getUser 为空的原因)