Skip to content

安全与 RBAC

CoSky 实现了全面的三层安全模型 -- 认证授权审计 -- 基于 CoSec 安全框架构建。用户凭据使用 SHA-256 哈希存储在 Redis 中。授权结合了策略引擎(CoSec 策略 JSON)和命名空间范围的 RBAC。所有操作都会被审计并持久化到 Redis List 中以供查询。

一览

层级组件职责关键文件源码
认证UserPasswordAuthentication通过用户名/密码登录UserPasswordAuthentication.ktsecurity/.../UserPasswordAuthentication.kt:11
认证RefreshTokenAuthentication刷新 JWT 令牌对RefreshTokenAuthentication.ktsecurity/.../RefreshTokenAuthentication.kt:14
授权CoSkyAuthorization双层授权(策略 + RBAC)CoSkyAuthorization.ktsecurity/.../CoSkyAuthorization.kt:19
RBACRbacServiceRedis 中的角色/资源 CRUDRbacService.ktsecurity/.../RbacService.kt:30
审计AuditLogHandlerInterceptor用于审计日志的 WebFilterAuditLogHandlerInterceptor.ktsecurity/.../AuditLogHandlerInterceptor.kt:31
用户管理UserService用户 CRUD、登录锁定、角色绑定UserService.ktsecurity/.../UserService.kt:36

三层安全架构

mermaid
flowchart LR
    subgraph "第 1 层:认证"
        UserPwd["UserPasswordAuthentication<br>SHA-256 凭据"]
        RefreshTok["RefreshTokenAuthentication<br>JWT 刷新"]
        AuthCtrl["AuthenticateController<br>/v1/authenticate"]
    end

    subgraph "第 2 层:授权"
        Policy["CoSkyPolicy<br>cosky-policy.json"]
        Authz["CoSkyAuthorization<br>策略 + RBAC"]
        NSApp["NamespaceRequestAttributesAppender<br>从路径提取命名空间"]
    end

    subgraph "第 3 层:审计"
        AuditFilter["AuditLogHandlerInterceptor<br>WebFilter"]
        AuditSvc["AuditLogService<br>持久化到 Redis List"]
    end

    AuthCtrl --> UserPwd
    AuthCtrl --> RefreshTok
    Authz --> Policy
    Authz --> NSApp
    AuditFilter --> AuditSvc

认证

CoSky 支持两种认证方式:

  1. UserPasswordAuthentication -- 使用 SHA-256 哈希凭据(存储在 Redis 中)验证用户名/密码。成功时,UserService.login() 方法返回包含用户角色绑定的 SimplePrincipal
  2. RefreshTokenAuthentication -- 接受访问/刷新令牌对,并通过 CoSec 的 TokenVerifier 验证刷新令牌。成功时返回新的令牌对。

AuthenticateController 端点

方法路径描述源码
POST/v1/authenticate/{username}/login使用密码登录AuthenticateController.kt:37
POST/v1/authenticate/{username}/refresh刷新令牌对AuthenticateController.kt:47

登录锁定机制

UserService.login() 实现了渐进式账户锁定以防止暴力破解攻击:

  • 最大失败次数:10 次(MAX_LOGIN_ERROR_TIMES
  • 基础锁定时长:15 分钟(LOGIN_LOCK_EXPIRE
  • 最大锁定时长:3 天(MAX_LOGIN_LOCK_EXPIRE
  • 指数退避lockoutDuration = baseLockout * max(tryCount / maxErrorTimes, 1),不超过最大值。
  • 锁定追踪:Redis 键(system:login_lock:{username})在每次失败尝试时递增,在成功登录时删除。
  • 解锁:管理员可通过 DELETE /v1/users/{username}/unlock 解锁用户。

源码: UserService.kt:135-177

登录流程

mermaid
sequenceDiagram
    autonumber
    participant Client as 客户端
    participant AuthenticateController
    participant TokenCompositeAuth
    participant UserPasswordAuth
    participant UserService
    participant Redis

    Client->>AuthenticateController: POST /v1/authenticate/{username}/login
    AuthenticateController->>TokenCompositeAuth: authenticateAsToken(UserPasswordCredentials)
    TokenCompositeAuth->>UserPasswordAuth: authenticate(credentials)
    UserPasswordAuth->>UserService: login(username, pwd)
    UserService->>Redis: INCR system:login_lock:{username}
    Redis-->>UserService: tryCount
    alt tryCount > 10
        UserService-->>UserPasswordAuth: SecurityException(账户已冻结)
    else 在限制内
        UserService->>Redis: GET system:user_idx(哈希密码)
        Redis-->>UserService: storedHash
        alt SHA-256(pwd) != storedHash
            UserService->>Redis: EXPIRE 锁定键(退避时长)
            UserService-->>UserPasswordAuth: SecurityException(密码错误)
        else 密码匹配
            UserService->>Redis: DEL system:login_lock:{username}
            UserService->>Redis: SMEMBERS system:user_role_bind:{username}
            Redis-->>UserService: roleSet
            UserService-->>UserPasswordAuth: SimplePrincipal(id, roles)
        end
    end
    TokenCompositeAuth->>TokenCompositeAuth: 生成 JWT 访问 + 刷新令牌
    TokenCompositeAuth-->>AuthenticateController: CompositeToken
    AuthenticateController-->>Client: 200 CompositeToken

授权

CoSky 使用 CoSkyAuthorization 实现的双层授权模型

  1. 第 1 层 -- 基于策略:CoSec 策略引擎根据 cosky-policy.json 评估请求。如果策略明确允许该请求,则立即通过。如果明确拒绝,则被拒绝。
  2. 第 2 层 -- RBAC:如果策略结果为隐式(既不允许也不拒绝),系统落入命名空间范围的 RBAC。它通过 NamespaceRequestAttributesAppender 从请求路径中提取命名空间,然后检查用户的角色是否授予了在该命名空间上所需的操作权限。

超级用户(cosky)绕过所有授权检查。

源码: CoSkyAuthorization.kt:24-60

授权决策流程

mermaid
flowchart TD
    Request["传入请求"] --> IsRoot{"是否为超级用户<br>(cosky)?"}
    IsRoot -- 是 --> Allow["允许"]
    IsRoot -- 否 --> Policy{"CoSec 策略<br>验证"}
    Policy -- "明确允许" --> Allow
    Policy -- "明确拒绝" --> ExplicitDeny["明确拒绝"]
    Policy -- "隐式(无匹配)" --> ExtractNS["从请求路径<br>提取命名空间"]
    ExtractNS --> HasNS{"找到命名空间?"}
    HasNS -- 否 --> ImplicitDeny["隐式拒绝"]
    HasNS -- 是 --> MapAction["将 HTTP 方法映射为操作<br>GET -> READ<br>PUT/POST/DELETE -> WRITE"]
    MapAction --> CheckRoles["检查用户角色<br>与 ResourceAction"]
    CheckRoles --> Match{"角色授予<br>权限?"}
    Match -- 是 --> Allow
    Match -- 否 --> ImplicitDeny

CoSkyPolicy 和 InitialPolicyLoader

CoSkyPolicy 从 CoSky 自身的配置服务加载安全策略。策略存储在 system 命名空间中的配置项中。它订阅配置变更事件,并在更新时自动刷新策略缓存。

如果配置服务中不存在策略,InitialPolicyLoader 会从 classpath 加载内置的 cosky-policy.json 作为回退。

源码: CoSkyPolicy.kt:17, InitialPolicyLoader.kt:7

RBAC 模型

CoSky 实现了命名空间范围的基于角色的访问控制模型:

  • 角色 -- 拥有名称、描述以及命名空间到 ResourceAction 绑定的映射。
  • ResourceAction -- 将 namespaceAction 枚举值配对。
  • Action -- 可以是 READr)、WRITEw)或 READ_WRITErw)。HTTP 方法映射为:GET/OPTIONS/TRACE/HEAD 映射为 READPOST/PUT/DELETE/PATCH 映射为 WRITE

内置的 admin 角色没有资源-操作绑定,作为系统保留角色拥有最高权限级别(由策略授予完全访问权限)。

mermaid
classDiagram
    class Role {
        +String roleName
        +String desc
        +Map~String, ResourceAction~ resourceActionBind
        +check(ResourceAction): Boolean
    }
    class ResourceAction {
        +String namespace
        +Action action
        +check(ResourceAction): Boolean
    }
    class Action {
        <<enumeration>>
        READ = r
        WRITE = w
        READ_WRITE = rw
        +check(Action): Boolean
        +asAction(String): Action
        +httpMethodAsAction(String): Action
    }
    class RbacService {
        +saveRole(roleName, SaveRoleRequest): Mono~Void~
        +removeRole(roleName): Mono~Boolean~
        +allRole: Mono~Set~RoleDto~~
        +getRole(roleName): Mono~Role~
        +getResourceBind(roleName): Flux~ResourceAction~
        +getRoleNamespaces(roles): Flux~String~
    }
    class RoleController {
        +allRole(): Mono~Set~RoleDto~~
        +getResourceBind(roleName): Mono~List~
        +saveRole(roleName, SaveRoleRequest): Mono~Void~
        +removeRole(roleName): Mono~Boolean~
    }

    Role --> ResourceAction : "1 对 n"
    ResourceAction --> Action
    RoleController --> RbacService
    RbacService --> Role : 加载,保存

超级用户和管理员角色

  • 超级用户cosky 用户(root)绕过所有授权。当 cosky.security.enforce-init-super-usertrue 时,由 SecurityCommand 在应用启动时初始化。会生成一个随机 10 字符密码并打印到标准输出。
  • 管理员角色admin 角色是系统保留角色,由策略引擎的 admin 语句授予完全访问权限。它会自动包含在角色列表中。

源码: UserService.kt:38-52, Role.kt:31-34, SecurityCommand.kt:25

审计日志

AuditLogHandlerInterceptor

AuditLogHandlerInterceptor 是一个响应式 WebFilter,拦截所有 HTTP 请求。在响应写入后,它创建包含以下内容的 AuditLog 条目:

  • operator -- 用户名(来自安全上下文,或从登录路径提取)
  • ip -- 远程地址
  • path -- 请求 URI
  • action -- HTTP 方法名
  • status -- HTTP 响应状态码
  • msg -- 错误消息(如有)
  • opTime -- 毫秒级时间戳

该过滤器可通过 SecurityProperties.auditLog.action 配置。默认仅审计 WRITE 操作。设置为 READ_WRITErw)可审计所有操作,设置为 READr)可仅审计读操作。

源码: AuditLogHandlerInterceptor.kt:31-76

AuditLogService

AuditLogService 将审计日志条目以 JSON 字符串的形式持久化到 Redis List(system:audit:log)中。新条目推入头部(leftPush)。查询支持通过 range 进行偏移/限制分页。

源码: AuditLogService.kt:27-51

用户管理

UserService

UserService 完全在 Redis 中管理用户:

  • 用户索引:Redis Hash(system:user_idx),将用户名映射到 SHA-256 密码哈希。
  • 角色绑定:Redis Set(system:user_role_bind:{username}),存储分配给每个用户的角色名称。
  • 密码哈希:使用 Guava 的 Hashing.sha256() 进行 UTF-8 编码。
  • 登录锁定:参见上方登录锁定机制
  • Root 初始化initRoot(enforce) 使用随机密码创建或重置 cosky 超级用户。

源码: UserService.kt:36-212

SecurityCommand

SecurityCommand 是一个在应用启动时运行的 CommandLineRunner。它调用 UserService.initRoot() 来初始化超级用户。当 cosky.security.enforce-init-super-user 设置为 true 时会自动执行。

源码: SecurityCommand.kt:25-34

UserController 端点

方法路径描述源码
GET/v1/users列出所有用户及其角色绑定UserController.kt:42
POST/v1/users/{username}创建新用户UserController.kt:52
DELETE/v1/users/{username}移除用户UserController.kt:62
PATCH/v1/users/{username}/password修改密码UserController.kt:47
PATCH/v1/users/{username}/role绑定角色到用户UserController.kt:57
DELETE/v1/users/{username}/unlock解锁被锁定的用户UserController.kt:67

默认安全策略

内置的 cosky-policy.json 定义了基准安全策略。其语句按顺序评估:

json
{
  "statements": [
    { "name": "options",       "action": { "all": { "method": "OPTIONS" } } },
    { "name": "swaggerUI",     "action": { "path": { "method": "GET", "pattern": ["/swagger-ui/**", ...] } } },
    { "name": "dashboard",     "action": { "path": { "method": "GET", "pattern": ["/", "/index.html", ...] } } },
    { "name": "actuatorHealth","action": ["/actuator/health", "/actuator/health/*"] },
    { "name": "authenticate",  "action": ["/v1/authenticate/{username}/login", "/v1/authenticate/{username}/refresh"] },
    { "name": "namespace",     "action": { "path": { "method": "GET", "pattern": "/v1/namespaces/**" } },
                              "condition": { "authenticated": {} } },
    { "name": "admin",         "action": "*", "condition": { "inRole": { "value": "admin" } } },
    { "name": "root",          "action": "*", "condition": { "eq": { "part": "context.principal.id", "value": "cosky" } } }
  ]
}

关键策略规则:

  • 未认证访问:OPTIONS 请求、Swagger UI、静态 Dashboard 资源、Actuator 健康检查和认证端点无需登录即可访问。
  • 命名空间读取:任何已认证用户都可以读取命名空间数据(GET /v1/namespaces/**)。
  • 管理员角色admin 角色成员拥有对所有 API 的不受限制访问(action: "*")。
  • Root 用户cosky 用户无论角色绑定如何,都拥有不受限制的访问权限。

源码: cosky-rest-api/src/main/resources/cosky-policy.json

相关页面

参考

基于 Apache License 2.0 许可发布。