@Inner 注解使用及原理

注解功能说明

@Inner 注解用于控制微服务接口的访问权限,帮助决定哪些接口需要登录才能访问、哪些接口可以公开访问、哪些接口只能在系统内部调用。

@Inner 注解架构示意图

快速决策指南

选择合适的配置:

场景配置方式是否需要登录能否外部访问
用户个人中心不加注解✅ 需要✅ 可以
获取验证码@Inner(false)❌ 不需要✅ 可以
定时任务调用@Inner❌ 不需要❌ 不可以

三种使用场景

场景一:需要登录的接口(不加注解)

什么时候用:

  • 用户登录后才能访问的功能
  • 常规的增删改查操作
  • 需要验证用户身份的接口

怎么做:

@GetMapping("/user/profile")
public User getUserProfile() {
    // 不需要添加 @Inner 注解
    // 前端请求会自动携带 token
    return userService.getProfile();
}

工作流程:

  1. 前端发送请求,携带登录 token
  2. Gateway 网关验证 token 是否有效
  3. 验证通过后才能访问接口

场景二:公开接口(使用 @Inner(false))

什么时候用:

  • 获取验证码
  • 查看公告信息
  • 不需要登录的公开页面

怎么做:

@Inner(false)  // 重点:设置为 false
@GetMapping("/captcha")
public String getCaptcha() {
    // 任何人都可以访问,不需要登录
    return captchaService.generate();
}
免鉴权示例

特点:

  • 不需要 token
  • 可以直接通过网关访问
  • 适合完全公开的接口

场景三:系统内部调用(使用 @Inner)

什么时候用:

  • 定时任务需要调用的接口
  • 消息队列触发的接口
  • 服务之间的内部通信

怎么做:

@Inner  // 重点:只写 @Inner,默认为 true
@PostMapping("/order/sync")
public void syncOrder() {
    // 只能被系统内部调用
    // 外部无法直接访问
    orderService.sync();
}
内部调用示例

特点:

  • 只能被内部服务调用
  • 外部无法直接访问
  • 不需要用户 token

工作原理(了解即可)

注解定义

public @interface Inner {
    /**
     * true:  只能内部调用(默认)
     * false: 可以外部访问
     */
    boolean value() default true;
}

系统启动时做了什么

系统会自动扫描所有标记了 @Inner 的接口,并加入白名单:

// 系统启动时自动执行
public void afterPropertiesSet() {
    // 扫描所有接口
    // 找到带 @Inner 注解的接口
    // 自动添加到忽略列表(白名单)
}

运行时如何检查

通过 AOP 切面进行权限验证:

@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)) {
        throw new AccessDeniedException("禁止外部访问");
    }

    return point.proceed();
}

常见问题

问题 1:使用路径变量要小心

路径变量使用注意

系统会将路径变量替换为通配符 *,可能导致意外的接口暴露。建议使用更具体的路径设计,在使用 SecurityUtils.getUser() 时,确保接口未被错误地加入忽略列表。

示例代码:

@Inner
@GetMapping("/user/{id}")  // 注意:路径变量
public User getUser(@PathVariable Long id) {
    return userService.getById(id);
}

问题说明:

  • 系统会把 {id} 替换成通配符 *
  • 变成 /user/* 都可以访问
  • 可能导致接口意外暴露

解决方案:

// 方案 1: 使用请求参数代替路径变量
@Inner
@GetMapping("/user")
public User getUser(@RequestParam Long id) {
    return userService.getById(id);
}

// 方案 2: 使用更具体的路径
@Inner
@GetMapping("/internal/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.getById(id);
}

问题 2:接口设置为公开,但携带错误 token 时被拦截

现象:

@Inner(false)  // 设置为公开
@GetMapping("/public/data")
public String getData() {
    return "public data";
}

访问时:

  • 不带 token → ✅ 正常访问
  • 带错误 token → ❌ 被拦截

原因:

Spring Security 默认会验证请求中的 token,即使接口是公开的。

解决方案:

如果需要完全忽略 token 验证,可以关闭 Spring Security 的这个行为:

配置示例