How CoSky Implements Role-Based Access Control (RBAC): A Deep Dive into the Redis-Backed Security Model

CoSky implements RBAC using a reactive, Redis-backed architecture where roles and their namespace-action permissions are stored in Hash structures, exposed through a REST API via RoleController and managed by RbacService according to the ahoo-wang/cosky source code.

CoSky's Role-Based Access Control (RBAC) system provides lightweight, scalable permission management for the service registry and configuration center. Implemented in the cosky-rest-api module, this security layer uses Redis as the persistence store and Spring WebFlux for non-blocking reactive operations. The design centers on simple Hash maps that bind roles to resource-specific actions, enabling fast authorization checks without external dependencies.

Redis Data Model for CoSky RBAC

The implementation persists authorization data using two primary Redis Hash structures under the system namespace (defined by Namespaced.SYSTEM).

Key Structure and Naming Convention

Redis Key Type Purpose
system:role_idx Hash Global index mapping roleName to roleDescription
system:role_resource_bind:{roleName} Hash Stores namespace → action pairs for each specific role

Each role receives its own dedicated Hash key for resource bindings, allowing atomic updates to permissions without affecting other roles. The system:role_idx Hash serves as the authoritative registry of all available roles in the system.

Core RBAC Service Layer

The RbacService class located at cosky-rest-api/src/main/kotlin/me/ahoo/cosky/rest/security/rbac/RbacService.kt encapsulates all reactive CRUD operations against Redis.

Role Lifecycle Management

saveRole(roleName: String, request: SaveRoleRequest) orchestrates atomic role creation or updates through three sequential operations:

  1. Writes the role description to system:role_idx using opsForHash.put(ROLE_IDX, roleName, request.desc)
  2. Removes existing permission bindings via delete(ROLE_RESOURCE_BIND_KEY)
  3. Iterates request.resourceActionBind to populate the new Hash with namespace → action pairs

removeRole(roleName: String) performs cleanup by removing the role from the global index and deleting its dedicated resource binding key, returning Mono<Boolean> indicating whether the role existed.

allRole() retrieves all entries from ROLE_IDX, aggregates them into Set<RoleDto>, and always injects a built-in ADMIN role into the returned collection regardless of Redis state.

Permission Resolution Methods

getRole(roleName) constructs the complete Role domain object by fetching the description from the global index and collecting all ResourceAction objects from the role-specific Hash.

getRoleNamespaces(roles: Set<String>) provides authorization filtering by flattening the distinct namespaces accessible across multiple role assignments. This method returns Flux<String> suitable for reactive stream processing in security filters.

REST API for Role Management

The RoleController at cosky-rest-api/src/main/kotlin/me/ahoo/cosky/rest/security/rbac/RoleController.kt exposes a thin HTTP layer over RbacService.

Endpoint Method Description
/roles GET Returns Set<RoleDto> including the built-in ADMIN role
/roles/{roleName}/bind GET Returns List<ResourceActionDto> showing namespace-action permissions
/roles/{roleName} PUT Accepts SaveRoleRequest JSON to create or replace a role
/roles/{roleName} DELETE Removes the role and its bindings, returning deletion status

The controller uses standard Spring WebFlux annotations and delegates all business logic to the service layer, maintaining separation of concerns.

Data Transfer Objects and Domain Model

The API separates internal domain models from external contracts using dedicated DTOs:

Practical Usage Examples

Creating a Role with Namespace Permissions

To define a role named "editor" with write access to article and comment namespaces:

curl -X PUT "http://localhost:8080/roles/editor" \
  -H "Content-Type: application/json" \
  -d '{
    "desc": "Content editor with write permissions",
    "resourceActionBind": [
      ["article", "WRITE"],
      ["comment", "WRITE"]
    ]
  }'

This creates:

  • Entry in system:role_idx: editor → "Content editor with write permissions"
  • Hash system:role_resource_bind:editor with fields article=WRITE and comment=WRITE

Querying Effective Namespaces for a User

To determine all accessible namespaces for a user holding multiple roles:

val rbacService: RbacService = // injected bean
val userRoles = setOf("editor", "viewer")

rbacService.getRoleNamespaces(userRoles)
    .collectList()
    .subscribe { namespaces ->
        println("Accessible namespaces: $namespaces")
        // Output: [article, comment, user-profile]
    }

The getRoleNamespaces method aggregates permissions across all provided roles, returning distinct namespace strings suitable for resource filtering.

Summary

  • CoSky RBAC uses Redis Hash structures to store role definitions (system:role_idx) and their permission bindings (system:role_resource_bind:{roleName}).
  • RbacService provides reactive CRUD operations using ReactiveStringRedisTemplate, ensuring non-blocking I/O throughout the security layer.
  • The REST API exposes role management through RoleController with endpoints for listing, binding, creating, and deleting roles.
  • A built-in ADMIN role is automatically included in all role listings, providing default superuser access.
  • The system supports namespace-level authorization, allowing fine-grained control over service registry and configuration resources.

Frequently Asked Questions

How does CoSky store role permissions in Redis?

CoSky stores RBAC data in two Redis Hash keys under the system namespace. The system:role_idx Hash maps role names to descriptions, while each role has a dedicated Hash at system:role_resource_bind:{roleName} containing field-value pairs of namespace → action (such as article → WRITE).

What is the purpose of the SaveRoleRequest class in CoSky's RBAC system?

The SaveRoleRequest class serves as the Data Transfer Object for creating or updating roles through the REST API. It contains a desc field for the role description and a resourceActionBind list of pairs mapping resource namespaces to permitted actions, which RbacService persists to Redis.

Does CoSky RBAC support reactive programming patterns?

Yes, the entire RBAC implementation is built on Spring WebFlux and Project Reactor. The RbacService class returns Mono and Flux types for all operations, using ReactiveStringRedisTemplate to perform non-blocking Redis interactions suitable for high-throughput reactive applications.

How does CoSky handle the default administrator role?

CoSky automatically injects a built-in ADMIN role into every response from the allRole() method. This hardcoded role exists independently of Redis storage and ensures that administrative access is always available regardless of the dynamic role configuration stored in system:role_idx.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →