@Inner 注解使用及原理

pclin
17
2025-01-09

@Inner 注解概述

@Inner 注解用于控制服务间调用的权限和访问控制。它提供了一种优雅的方式来处理微服务架构中的认证需求。

架构示意图

#使用场景

#1. 标准鉴权访问(默认模式)

  • 1.通过 Gateway 访问,需要完整的认证流程,适用于常规的 CRUD 操作

  • 2.前端发起带 token 的请求,涉及 feign 服务间调用,feign 会自动传递 token

  • 无需添加 @Inner 注解

#2. 免鉴权外部访问

  • 通过 Gateway 访问,但无需认证

  • 适用场景:获取验证码、公开接口等

  • 使用方式:@Inner(false)

免鉴权示例

#3. 内部服务调用 (无 token)

  • 某些接口未携带 token,涉及 feign 服务间调用

  • 定时任务通过 feign 调用接口、MQ 调用接口等

  • 使用方式:@Inner

内部调用示例

#技术实现原理

#1. 注解定义

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
	/**
	 * 是否仅允许服务间调用
	 * true: 仅允许服务间调用
	 * false: 允许外部直接访问
	 */
	boolean value() default true;
}

#2. 自动配置机制

系统启动时会自动扫描带有 @Inner 注解的接口,并将其添加到白名单:

@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);
			// 处理方法级注解
			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))));
			// 处理类级注解
			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))));
		});
	}
}

#3. 安全控制实现

通过 AOP 实现运行时的安全检查:

@Slf4j
@Aspect
@Component
@AllArgsConstructor
public class PigxSecurityInnerAspect {
	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();
	}
}

#最佳实践与注意事项

  1. 路径变量使用警告

@Inner
@GetMapping("/info/{username}")  // 会生成 /info/* 的忽略规则

使用路径变量时要特别注意

  • 系统会将路径变量替换为通配符 *

  • 可能导致意外的接口暴,建议使用更具体的路径设计

  • 在使用 SecurityUtils.getUser() 时,确保接口未被错误地加入忽略列表

  1. 推荐用法

  • 仅在必要的接口上使用 @Inner

  • 优先考虑更具体的路径而非通配符

  • 定期审查带有 @Inner 注解的接口

动物装饰