Skip to content

@Public 免登录与白名单

宠物商店里总有一些接口需要在用户登录前就能访问:宠物列表的公开浏览、健康检查探针、Swagger 文档页面……这些都不应该被鉴权拦截器挡在门外。forge 提供两条互补的放行通道:方法/类级别的 @Public 注解,以及各模块通过 AuthWhiteListProvider SPI 贡献、最终聚合放行的白名单。本篇带你把它们用起来。

关联阅读

放行通道为什么这样分层、SPI 如何聚合,参见设计篇 @Public 与白名单聚合设计。涉及 SPI 扫描机制可看 SPI 扩展点

两种放行方式怎么选

方式适用场景粒度
@Public 注解自己写的 Controller 方法/类要免登录精确到方法或类
AuthWhiteListProvider SPI模块对外贡献一批固定免登录路径路径模式(支持 /**
forge.auth.white-list 配置运维侧临时追加放行路径,不改代码路径模式

简单记:自己的代码用 @Public,整个模块成批放行用 SPI,临时调整用配置。

用 @Public 标记免登录接口

在宠物商店的 Controller 上,给需要匿名访问的方法加 @Public 即可。注解既可标在方法上,也可标在类上(类级则整个 Controller 全部免登录)。

java
package com.demo.petshop.controller;

import cn.cvking.forge.auth.annotation.Public;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/v1/api/pet")
public class PetPublicController {

    // 公开的宠物列表,无需登录即可浏览
    @Public
    @GetMapping("/on-sale")
    public List<Pet> listOnSale() {
        // 仅返回在售宠物
        return Models.origin(Pet.class)
                .query()
                .eq("status", PetStatus.ON_SALE)
                .list();
    }

    // 未加 @Public 的接口仍需登录,例如下架操作
    @GetMapping("/{id}/off-shelf")
    public void offShelf(@PathVariable Long id) {
        // 需要携带令牌才能访问
    }
}

@Public 的判定优先级

拦截器在最前面就检查 @Public,命中即放行,不再校验令牌。方法级标注优先于类级。正因为它「优先且就近」,是否放行一眼可见,但也意味着误加 @Public 会让接口彻底裸奔——加之前务必确认该接口确实可匿名访问。

用 AuthWhiteListProvider 成批贡献白名单

当一个模块需要放行一组固定路径(比如某个公开门户的所有只读接口),与其在每个 Controller 上散落 @Public,不如实现 AuthWhiteListProvider SPI,一次性把路径模式贡献出来。

第一步,实现接口:

java
package com.demo.petshop.auth;

import cn.cvking.forge.auth.spi.AuthWhiteListProvider;

import java.util.List;

public class PetShopWhiteListProvider implements AuthWhiteListProvider {

    @Override
    public List<String> contributeWhiteList() {
        // 公开门户:宠物公开浏览 + 分类树,统一免登录
        return List.of(
                "/v1/api/public/**",
                "/v1/api/pet/on-sale",
                "/v1/api/category/tree"
        );
    }
}

第二步,注册 SPI 实现,在 META-INF/services/ 下新建文件:

文件名 META-INF/services/cn.cvking.forge.auth.spi.AuthWhiteListProvider,内容为实现类全限定名:

com.demo.petshop.auth.PetShopWhiteListProvider

框架启动时会通过 Spider.getAllExtensions(AuthWhiteListProvider.class) 扫描所有实现,调用各自的 contributeWhiteList() 并聚合。多个模块各自贡献,互不干扰——这是聚合型 SPI,所有实现的结果会被合并,而不是互斥单选。

三层白名单如何聚合

最终被拦截器放行的路径,由三部分合并而成:

  1. 框架硬编码的基础白名单:/v1/api/auth/login/error/doc.html/swagger-ui/**/v3/api-docs/**/swagger-resources/**/webjars/**
  2. 配置文件 forge.auth.white-list 列出的路径。
  3. 所有 AuthWhiteListProvider 实现 contributeWhiteList() 贡献的路径。

合并后作为拦截器的 excludePathPatterns,对 /v1/**/v2/** 的请求生效。若没有任何 SPI 实现,聚合结果为空,对拦截器零副作用,一切照常工作。

用配置追加白名单的写法:

yaml
forge:
  auth:
    enabled: true
    white-list:
      - /v1/api/public/**
      - /v1/health

验证结果

为了在控制台看到放行链路,先开启 debug 日志:

yaml
logging:
  level:
    cn.cvking.forge.auth: debug

启动应用,控制台会先打印当前选用的鉴权实现(说明拦截器与放行机制已就位):

[auth] 使用鉴权实现: spring-security

随后访问一个已放行的接口,例如不带任何令牌请求公开的在售宠物列表:

bash
curl http://localhost:8080/v1/api/pet/on-sale

因为该方法标了 @Public,请求被直接放行,正常返回宠物数据,控制台不会出现令牌缺失相关错误。

作为对照,访问一个未放行、需要登录的接口而不带令牌:

bash
curl http://localhost:8080/v1/api/pet/1/off-shelf

拦截器校验失败,返回鉴权错误码 40100(未携带令牌,请先登录)。把这两次请求的结果对比,即可确认放行与拦截各自生效。

常见反例与排查

@Public 加了却仍被拦截

现象:方法已标 @Public,请求却返回 40100(未携带令牌,请先登录)。

原因:导入的注解包名不对,误用了其它框架的同名注解;或注解加在了非 Controller 处理方法上(拦截器是基于处理方法上的注解判定的)。

修复:确认导入的是 cn.cvking.forge.auth.annotation.Public,且注解直接落在被路由到的 Controller 方法(或其所在类)上。

白名单路径不生效,请求仍要求登录

现象:在 forge.auth.white-list 或 SPI 里配了路径,访问却被拦截。

原因:路径前缀不匹配。拦截器只对 /v1/**/v2/** 生效,且白名单是按路径模式匹配的;若实际请求路径与配置写法不一致(例如漏写 /v1 前缀、把单段路径写成了精确匹配却想匹配子路径),就匹配不上。

修复:核对完整请求路径,需要匹配子路径时使用 /** 通配(如 /v1/api/public/**),并确保前缀落在 /v1/v2 范围内。

SPI 贡献的白名单完全没被加载

现象:实现了 AuthWhiteListProvider,但贡献的路径一个都没放行。

原因:模块没有注册 SPI——缺少 META-INF/services/cn.cvking.forge.auth.spi.AuthWhiteListProvider 文件,或文件内类名拼写错误,导致 Spider.getAllExtensions 扫不到该实现。

修复:检查 META-INF/services/ 下文件名是否等于接口全限定名,文件内容是否为实现类的正确全限定名(一行一个)。

误把需鉴权接口放进了白名单

现象:本应登录才能访问的接口(如下架、删除)可被匿名调用。

原因:白名单用 /** 通配过宽,把本应受保护的路径一并放行;或 @Public 加在了类级,导致整个 Controller 都免登录。

修复:收窄通配范围,按最小放行原则只暴露确实公开的路径;类级 @Public 仅用于整体公开的 Controller,混合接口请改用方法级标注。