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:
- Writes the role description to
system:role_idxusingopsForHash.put(ROLE_IDX, roleName, request.desc) - Removes existing permission bindings via
delete(ROLE_RESOURCE_BIND_KEY) - Iterates
request.resourceActionBindto populate the new Hash withnamespace → actionpairs
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:
SaveRoleRequest(cosky-rest-api/.../rbac/SaveRoleRequest.kt): Request payload containingdesc: StringandresourceActionBind: List<Pair<String, String>>RoleDto(cosky-rest-api/.../rbac/RoleDto.kt): Read-only view with role name and descriptionResourceActionDto(cosky-rest-api/.../rbac/ResourceActionDto.kt): Exposes permission tuples asnamespaceandactionstringsRoleandResourceAction(cosky-rest-api/.../rbac/Role.kt,ResourceAction.kt): Internal domain representations used byRbacService
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:editorwith fieldsarticle=WRITEandcomment=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}). RbacServiceprovides reactive CRUD operations usingReactiveStringRedisTemplate, ensuring non-blocking I/O throughout the security layer.- The REST API exposes role management through
RoleControllerwith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →