搜索 K
Appearance
Appearance
宠物商店里总有一些接口需要在用户登录前就能访问:宠物列表的公开浏览、健康检查探针、Swagger 文档页面……这些都不应该被鉴权拦截器挡在门外。forge 提供两条互补的放行通道:方法/类级别的 @Public 注解,以及各模块通过 AuthWhiteListProvider SPI 贡献、最终聚合放行的白名单。本篇带你把它们用起来。
关联阅读
放行通道为什么这样分层、SPI 如何聚合,参见设计篇 @Public 与白名单聚合设计。涉及 SPI 扫描机制可看 SPI 扩展点。
| 方式 | 适用场景 | 粒度 |
|---|---|---|
@Public 注解 | 自己写的 Controller 方法/类要免登录 | 精确到方法或类 |
AuthWhiteListProvider SPI | 模块对外贡献一批固定免登录路径 | 路径模式(支持 /**) |
forge.auth.white-list 配置 | 运维侧临时追加放行路径,不改代码 | 路径模式 |
简单记:自己的代码用 @Public,整个模块成批放行用 SPI,临时调整用配置。
在宠物商店的 Controller 上,给需要匿名访问的方法加 @Public 即可。注解既可标在方法上,也可标在类上(类级则整个 Controller 全部免登录)。
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 会让接口彻底裸奔——加之前务必确认该接口确实可匿名访问。
当一个模块需要放行一组固定路径(比如某个公开门户的所有只读接口),与其在每个 Controller 上散落 @Public,不如实现 AuthWhiteListProvider SPI,一次性把路径模式贡献出来。
第一步,实现接口:
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,所有实现的结果会被合并,而不是互斥单选。
最终被拦截器放行的路径,由三部分合并而成:
/v1/api/auth/login、/error、/doc.html、/swagger-ui/**、/v3/api-docs/**、/swagger-resources/**、/webjars/**。forge.auth.white-list 列出的路径。AuthWhiteListProvider 实现 contributeWhiteList() 贡献的路径。合并后作为拦截器的 excludePathPatterns,对 /v1/**、/v2/** 的请求生效。若没有任何 SPI 实现,聚合结果为空,对拦截器零副作用,一切照常工作。
用配置追加白名单的写法:
forge:
auth:
enabled: true
white-list:
- /v1/api/public/**
- /v1/health为了在控制台看到放行链路,先开启 debug 日志:
logging:
level:
cn.cvking.forge.auth: debug启动应用,控制台会先打印当前选用的鉴权实现(说明拦截器与放行机制已就位):
[auth] 使用鉴权实现: spring-security随后访问一个已放行的接口,例如不带任何令牌请求公开的在售宠物列表:
curl http://localhost:8080/v1/api/pet/on-sale因为该方法标了 @Public,请求被直接放行,正常返回宠物数据,控制台不会出现令牌缺失相关错误。
作为对照,访问一个未放行、需要登录的接口而不带令牌:
curl http://localhost:8080/v1/api/pet/1/off-shelf拦截器校验失败,返回鉴权错误码 40100(未携带令牌,请先登录)。把这两次请求的结果对比,即可确认放行与拦截各自生效。
现象:方法已标 @Public,请求却返回 40100(未携带令牌,请先登录)。
原因:导入的注解包名不对,误用了其它框架的同名注解;或注解加在了非 Controller 处理方法上(拦截器是基于处理方法上的注解判定的)。
修复:确认导入的是 cn.cvking.forge.auth.annotation.Public,且注解直接落在被路由到的 Controller 方法(或其所在类)上。
现象:在 forge.auth.white-list 或 SPI 里配了路径,访问却被拦截。
原因:路径前缀不匹配。拦截器只对 /v1/**、/v2/** 生效,且白名单是按路径模式匹配的;若实际请求路径与配置写法不一致(例如漏写 /v1 前缀、把单段路径写成了精确匹配却想匹配子路径),就匹配不上。
修复:核对完整请求路径,需要匹配子路径时使用 /** 通配(如 /v1/api/public/**),并确保前缀落在 /v1、/v2 范围内。
现象:实现了 AuthWhiteListProvider,但贡献的路径一个都没放行。
原因:模块没有注册 SPI——缺少 META-INF/services/cn.cvking.forge.auth.spi.AuthWhiteListProvider 文件,或文件内类名拼写错误,导致 Spider.getAllExtensions 扫不到该实现。
修复:检查 META-INF/services/ 下文件名是否等于接口全限定名,文件内容是否为实现类的正确全限定名(一行一个)。
现象:本应登录才能访问的接口(如下架、删除)可被匿名调用。
原因:白名单用 /** 通配过宽,把本应受保护的路径一并放行;或 @Public 加在了类级,导致整个 Controller 都免登录。
修复:收窄通配范围,按最小放行原则只暴露确实公开的路径;类级 @Public 仅用于整体公开的 Controller,混合接口请改用方法级标注。