# API 鉴权与权限修改规范

## 适用范围

- 新增接口时，需要判定它是“公开访问”“仅登录可访问”还是“登录且有权限才可访问”时，使用本文。
- 已有接口在“不需要登录 / 需要登录 / 需要权限”之间切换时，使用本文。

## 现有鉴权基线

- `initialize/router.go` 当前只创建两类顶层路由组：
  - `PublicGroup`：不挂鉴权中间件。
  - `PrivateGroup`：统一挂 `middleware.JWTAuth()` 和 `middleware.CasbinHandler()`。
- `JWTAuth` 定义在 `middleware/jwt.go`。
- `CasbinHandler` 定义在 `middleware/casbin_rbac.go`。
- 结论：
  - 挂到 `PublicGroup` = 不需要登录。
  - 挂到 `PrivateGroup` = 需要登录，且需要角色权限。
  - 当前仓库没有默认内置的“只登录、不校验权限”公共组。
  - 不存在“只校验权限、不登录”的合法模式；`CasbinHandler` 依赖 `JWTAuth` 写入的用户 claims。

## 权限模式判定

| 模式 | 路由挂载方式 | JWT | Casbin | 后续要求 |
|:---|:---|:---|:---|:---|
| 不需要登录 | `PublicGroup` / `PublicRouter` | 否 | 否 | 默认不作为角色权限点长期维护 |
| 需要登录，不需要权限 | 基于 `PublicRouter` 单独加 `middleware.JWTAuth()` | 是 | 否 | 默认不进入角色权限维护 |
| 需要登录且需要权限 | `PrivateGroup` / `Router` | 是 | 是 | 必须进入 `sys_apis` 并分配角色 |

## 强制规则

- 同一个 `path + method` 只能落一种鉴权模式；禁止同时注册到公开组和鉴权组。
- `middleware.OperationRecord()` 只负责操作审计，不改变登录与权限判定。
- 鉴权逻辑统一落在 `router` 层；禁止在 `API` 或 `Service` 中靠 `if token != ""`、`if authorityId == ...` 兜底模拟权限。
- 修改接口鉴权时，Swagger 注解必须同步：
  - 不需要登录：移除 `@Security ApiKeyAuth`
  - 需要登录：保留 `@Security ApiKeyAuth`
- 业务路由统一在 `router/<module>` 调整；业务模块接入点统一在 `initialize/router_biz.go` 注册。

## 改成不需要登录

- 把路由挂载从 `PrivateGroup` 或“仅登录组”移到 `PublicRouter`。
- 如果只是业务模块内部路由调整，不要改 `API`、`Service` 方法签名。
- 该接口如果不再作为后台角色权限点维护，必须把对应 `path + method` 从权限维护中移走，二选一即可：
  - 删除对应 `sys_apis` 记录。
  - 加入 `sys_ignore_apis`，避免后续 `SyncApi` 再次纳入权限页。
- 若历史上已经给该接口分配过角色，必须同步清理对应 Casbin 规则，避免后台权限配置出现“看起来要授权、实际上公开可调”的假象。

## 改成需要登录，不需要权限

- 当前项目没有全局现成的 `LoginGroup`；少量接口优先在对应 router 中基于 `PublicRouter` 单独挂 `JWTAuth`，不要为了一两个接口改全局入口。
- 推荐写法：

```go
func (r *BookRouter) InitBookRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
    bookLoginRouter := PublicRouter.Group("book").Use(middleware.JWTAuth())
    {
        bookLoginRouter.GET("profile", bookApi.GetProfile)
    }
}
```

- 同一模块里如果“仅登录接口”很多，再考虑在 `initialize/router.go` 新增单独的 `LoginGroup`，并把它显式传入对应 router 初始化函数。
- 这类接口禁止挂 `middleware.CasbinHandler()`。
- 这类接口默认不进入 `sys_apis` 的角色权限维护；如果历史上已经进入，必须删除或加入 `sys_ignore_apis`，不要继续让后台把它当作“需要分配角色”的接口。

## 改成需要登录且需要权限

- 把路由挂到 `PrivateGroup` 或业务 router 的 `Router` 参数上。
- 只把路由挪进 `PrivateGroup` 还不够；`CasbinHandler` 按 `path + method` 校验权限，没有角色策略就会直接返回“权限不足”。
- 因此必须同时完成以下动作：
  - 确保该 `path + method` 存在于 `sys_apis`。
  - 确保该接口已分配允许访问的角色。
  - 刷新 Casbin 策略缓存。
- 系统现成接口的完整路径 = `<router-prefix>/api/...`；如果 `router-prefix` 为空，则完整路径就是 `/api/...`。不要把是否存在额外前缀写死成项目事实。
- 常用管理接口：
  - `GET <router-prefix>/api/syncApi`：对比内存路由和 `sys_apis`
  - `POST <router-prefix>/api/enterSyncApi`：确认把新增路由写入 `sys_apis`
  - `POST <router-prefix>/api/setApiRoles`：全量覆盖某个接口的角色列表
  - `GET <router-prefix>/api/freshCasbin`：刷新 Casbin 策略缓存
- 若接口此前在 `sys_ignore_apis` 中，改成“需要权限”前必须先取消 ignore，再同步到 `sys_apis`。
- 若不给任何角色分配该接口，则结果不是“仅登录可访问”，而是“所有已登录用户都权限不足”。

## 验证要求

- 修改完成后，至少验证下面三类请求结果：
  - 公开接口：不带 token 可访问成功。
  - 仅登录接口：不带 token 返回未登录；带有效 token 可访问成功。
  - 权限接口：不带 token 返回未登录；带无权角色 token 返回“权限不足”；带有权角色 token 可访问成功。
- 如果改了接口鉴权方式，同时该接口出现在 Swagger、前端权限配置页、角色配置页，也必须同步核对展示结果是否一致。

## 禁止事项

- 禁止只改 `sys_apis` 或 `casbin_rule`，却不改真实 router 挂载。
- 禁止只把路由从 `PublicGroup` 挪到 `PrivateGroup`，却不补 `sys_apis` 和角色策略。
- 禁止把“仅登录接口”偷懒挂到 `PrivateGroup`，再靠给所有角色放权来模拟“无权限限制”。
- 禁止让公开接口长期保留在角色权限维护页里，造成权限含义失真。
