spring-securityauthorizationsystem-designspring-authorization-server

Where should roles and missions/ACLs be managed when building an authorization server?


I’m trying to build my own Spring Authorization Server (for learning purposes and possibly to use across multiple projects in the future).

I’m already familiar with Spring Security and resource server concepts, but I’m confused about the architecture for roles and permissions (ACLs).

My goal:

Now I’m confused about the best place to store and enforce roles/permissions/ACLs:

  1. Authorization Server Approach (centralized, like Keycloak):

    • Store a mapping of Client → Roles → Permissions in the Authorization Server DB.

    • Issue JWTs with embedded roles/ACL claims.

    • All client apps/resource servers rely on these claims.

    • Centralized management, but could become complex if ACLs are very fine-grained (e.g., per resource ownership).

  2. Resource Server Approach

    • Authorization server only handles authentication (tokens, identity).

    • Each resource server/client API manages its own roles, permissions, and ACLs in its own database.

    • Allows domain-specific permissions but makes cross-service role management harder.

What I want to understand:


Solution

  • In what you describe, there is a confusion between roles, which are user attributes, and resource access.

    A centralized resource access decision service is a disaster in terms of:

    I define user roles on a per-client basis, and configure the authorization-server to put in tokens only the roles for the client requesting the tokens. Then, resource servers can take access control decision based on the access token claims (mainly user subject and roles) and the accessed resource, without the need of any other service.

    Keycloak's handles roles at the client level, Keycloak's authorization service is optional and deactivated by default, and Spring Security SpEL allows referencing secured method arguments.

    Let's say that we have an ImageRepo that can store ImageEntity instances based on their ID.

    private final ImageRepo imageRepo;
    
    @GetMapping("/my-images")
    @PreAuthorize("isAuthenticated() && hasAuthority('images.read')")
    public List<ImageEntity> getMyImages(JwtAuthenticationToken oauth) {
      return imageRepo.findByAuthor(oauth.getName());
    }
    
    @PutMapping("/images/{id}")
    @PreAuthorize("isAuthenticated() && #imageToUpdate.author == #oauth.name")
    public void updateImage(
        @PathVariable("id") ImageEntity imageToUpdate, // auto-magically resolved by the imageRepo from the ImageEntity ID
        @RequestPart MultipartFile dto, 
        JwtAuthenticationToken oauth) {
      ...
    }