微服务内部调用安全实践:基于 OpenFeign + 自定义注解 + AOP 的实现
·
在微服务架构下,服务之间往往需要频繁通信,例如用户服务调用订单服务、网关调用鉴权服务等。默认情况下,OpenFeign 调用虽然方便,但存在一个问题:缺乏内外部请求区分与安全校验机制。这意味着,如果接口未做保护,外部请求也可能伪造内部调用。
本文介绍一种基于 OpenFeign + 自定义注解 + AOP 拦截 + 请求拦截器 的安全设计方案,既保持了调用简洁性,又确保了服务间调用的安全性与可控性。
一、问题背景
OpenFeign 本质上是一个声明式的 HTTP 客户端,它会在运行时通过代理生成 HTTP 调用代码。
但问题在于:
- OpenFeign 调用和外部 REST 调用本质上没区别,都是 HTTP 请求;
- 服务端很难分辨请求来源是否“可信”;
- 如果内部接口暴露给外部,就可能被伪造访问。
因此,我们需要一种 “内部请求鉴权机制”,让系统能识别请求是否来自受信任的微服务。
二、方案设计思路
整体思路如下:
- 在目标接口上添加自定义注解
@InnerAuth,标识“仅允许内部访问”。 - 使用 AOP 切面拦截
@InnerAuth,校验请求头中是否带有内部标识。 - 对所有 OpenFeign 请求统一添加内部标识头(例如
from-source: inner)。 - 服务端切面验证通过后继续执行,否则直接抛出异常。
这样,内部服务请求会自动通过校验,而外部请求即使访问同一个接口也会被拒绝。
三、实现步骤
1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{
boolean isUser() default false; // 是否要求携带用户信息
}
2. AOP 切面验证逻辑
@Aspect
@Component
public class InnerAuthAspect implements Ordered {
@Around("@annotation(innerAuth)")
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable {
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
if (!StringUtils.equals(SecurityConstants.INNER, source)) {
throw new InnerAuthException("没有内部访问权限,不允许访问");
}
// 若需要校验用户信息
if (innerAuth.isUser()) {
String userId = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(username)) {
throw new InnerAuthException("未设置用户信息,不允许访问");
}
}
return point.proceed();
}
}
3. OpenFeign 拦截器
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest request = ServletUtils.getRequest();
if (request != null) {
Map<String, String> headers = ServletUtils.getHeaders(request);
// 添加内部标识
requestTemplate.header(SecurityConstants.FROM_SOURCE, SecurityConstants.INNER);
// 携带用户信息(可选)
String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
if (StringUtils.isNotEmpty(userId)) {
requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
}
String username = headers.get(SecurityConstants.DETAILS_USERNAME);
if (StringUtils.isNotEmpty(username)) {
requestTemplate.header(SecurityConstants.DETAILS_USERNAME, username);
}
}
}
}
其中RequestInterceptor 是 Feign 独有的接口:
- 专门用于拦截 Feign 客户端发出的 HTTP 请求
- 在请求发送前对请求进行统一处理(如添加请求头、认证信息等)
4. Feign 客户端示例
@FeignClient(name = "user-service") //user-service 是服务B在注册中心的名字
public interface RemoteUserService {
@GetMapping("/inner/user/info")
R<UserInfo> getUserInfo(@RequestParam("userId") Long userId);
}
5. 服务端接口
@RestController
@RequestMapping("/inner/user")
public class InnerUserController {
@Resource
private RemoteUserService UserService;
@InnerAuth(isUser = true)
@GetMapping("/info")
public R<UserInfo> getUserInfo(@RequestParam Long userId) {
return R.ok(userService.getById(userId));
}
}
四、调用流程解析
- 业务服务调用
RemoteUserService.getUserInfo(); - FeignRequestInterceptor 拦截 → 自动加上
from-source: inner; - 请求到达用户服务;
@InnerAuth切面拦截 → 验证请求头;- 校验通过 → 放行执行;
- 若 header 缺失或伪造 → 抛出
InnerAuthException。
五、总结
通过自定义注解 + AOP + OpenFeign 拦截器,我们实现了:
- ✅ 内部服务调用安全隔离;
- ✅ 统一的请求头传递机制;
- ✅ 可扩展的用户校验逻辑;
- ✅ 良好的可维护性与解耦性。
这种方式非常适合企业级微服务体系,能在保持 Feign 调用简洁性的同时,兼顾系统安全性与一致性。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)