在微服务架构下,服务之间往往需要频繁通信,例如用户服务调用订单服务、网关调用鉴权服务等。默认情况下,OpenFeign 调用虽然方便,但存在一个问题:缺乏内外部请求区分与安全校验机制。这意味着,如果接口未做保护,外部请求也可能伪造内部调用。

本文介绍一种基于 OpenFeign + 自定义注解 + AOP 拦截 + 请求拦截器 的安全设计方案,既保持了调用简洁性,又确保了服务间调用的安全性与可控性。

一、问题背景

OpenFeign 本质上是一个声明式的 HTTP 客户端,它会在运行时通过代理生成 HTTP 调用代码。
但问题在于:

  • OpenFeign 调用和外部 REST 调用本质上没区别,都是 HTTP 请求;
  • 服务端很难分辨请求来源是否“可信”;
  • 如果内部接口暴露给外部,就可能被伪造访问。

因此,我们需要一种 “内部请求鉴权机制”,让系统能识别请求是否来自受信任的微服务。

二、方案设计思路

整体思路如下:

  1. 在目标接口上添加自定义注解 @InnerAuth,标识“仅允许内部访问”。
  2. 使用 AOP 切面拦截 @InnerAuth,校验请求头中是否带有内部标识。
  3. 对所有 OpenFeign 请求统一添加内部标识头(例如 from-source: inner)。
  4. 服务端切面验证通过后继续执行,否则直接抛出异常。

这样,内部服务请求会自动通过校验,而外部请求即使访问同一个接口也会被拒绝。

三、实现步骤

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));
    }
    
}

四、调用流程解析

  1. 业务服务调用 RemoteUserService.getUserInfo()
  2. FeignRequestInterceptor 拦截 → 自动加上 from-source: inner
  3. 请求到达用户服务;
  4. @InnerAuth 切面拦截 → 验证请求头;
  5. 校验通过 → 放行执行;
  6. 若 header 缺失或伪造 → 抛出 InnerAuthException

五、总结

通过自定义注解 + AOP + OpenFeign 拦截器,我们实现了:

  • ✅ 内部服务调用安全隔离;
  • ✅ 统一的请求头传递机制;
  • ✅ 可扩展的用户校验逻辑;
  • ✅ 良好的可维护性与解耦性。

这种方式非常适合企业级微服务体系,能在保持 Feign 调用简洁性的同时,兼顾系统安全性与一致性。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐