Spring Security and Non-flat Roles Inheritance Architecture
Tags: java, programming, security, spring
Published: 2023-02-23 17:15:54

## Table of contents

1. Business requirements and domain model
1. Roles, enums, and inheritance
1. Unit testing roles inheritance
1. Defining JPA entities
1. Creating custom Authentication implementation
    1. Why does getAuthorities() return empty set?
    1. UserId, and volatile authenticated flag
1. Creating custom AuthenticationProvider
1. Defining Spring Security config
1. Declaring REST API methods
1. Creating custom role checking service
1. Combining PreAuthorize and custom role checking service
1. Short and elegant enum references in SpEL expressions
1. Integration testing and validating security

---

Then it comes to [authorization](https://www.okta.com/identity-101/authentication-vs-authorization/#:~:text=What's%20the%20difference%20between%20authentication,permission%20to%20access%20a%20resource.), roles always come into play. Flat ones are straightforward. User possess a set of privileges that you simply check for containing the required element. But what if a user can have different roles for specific entities? For example, I can be an editor in the particular social media post but only a viewer in another one. Also, there might be inheritance rules. If an admin grant me the editor role, I automatically become a viewer. But if I'm an editor, I'm not becoming an admin. What's the approach to handle the scenario to remain code clean and maintainable? Don't worry, I'm giving your answers.

In this article, I'm telling you:

1. What's the best approach to handle roles inheritance in Java?
2. How to test the stated hierarchy?
3. How to apply the solution in Spring Security and Spring Data JPA?

You can find the code examples and [the entire project setup in this repository](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example).

![Meme article cover](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1dtflgohjrjfm7nwyg6.jpeg)

## Business requirements and domain model

Supposing we’re developing a simple social media platform. Look at the diagram below. Here I described the business entities we’re working with in this article.

![Business entities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q5duzbiah8ti5s45hxu.png)

There are 3 core entities:

1. `User` is the one who reads existing posts and creates new ones.
1. `Community` is the feed to submit new posts.
1. `Post` is an individual part of media. Some users can view it, edit it, and delete it.

Also, we have role a model which is a bit more complicated than the plain privileges assignment. For example, a user can owe an `EDITOR` role for the particular post but has only a `VIEWER` for another one. Or user may be an `ADMIN` for the ‘Cats and dogs’ community but just a `MODERATOR` for the ‘Motorcycles and Heavy metal’ one.

The roles provide such privileges:

1. `CommunityRole.ADMIN` gives ultimate access to the community and the containing posts.
1. `CommunityRole.MODERATOR` provides an ability to add new posts and remove old ones.
1. `PostRole.EDITOR` allows to edit the content of the particular post.
1. `PostRole.REPORTER` gives the credit to send reports about inappropriate attitude in the comments.
1. `PostRole.VIEWER` grants an access to view the post and leave comments. 

However, business also wants the inheritance model. For example, if I have the root role, it means I also can do actions that any child provides. Look at the schema below to understand the approach.

![Roles inheritance model](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwapmgx1l3woznm4ytkw.png)

Suppose that I'm a `MODERATOR` in some community. That means, I'm `EDITOR`, `REPORTER`, and `VIEWER` as well for any other post in the community. On the contrary, if somebody granted me with the `REPORTER` role for the post, it doesn't mean I have the rights to edit it (I need the `EDITOR` for that) or add new posts (the `MODERATOR` role provides access for it).

That's a convenient approach. You don't have to check the set of many roles for every possible operation. You just have to validate the presence of the lowest authority. If I'm an `ADMIN` in the community, then I'm also a `VIEWER` for any post there. So, due to the inheritance model, you have to check the `VIEWER` and that's it.

Anyway, it doesn't seem like an simple task to implement it in the code. Besides, the `VIEWER` model has two parents: the `REPORTER` and the `EDITOR`. The Java doesn't allow multiple inheritance. Meaning that we need a special approach.

## Roles, enums, and inheritance

Roles are the perfect candidates for the enum. Look at the code snippet below.

```java
public enum CommunityRoleType {
    ADMIN, MODERATOR
}

public enum PostRoleType {
    VIEWER, EDITOR, REPORTER
}
```

How we can build an inheritance model with these simple enums? At first, we need an interface to bind the roles together. Look at the declaration below.

```java
public interface Role {
    boolean includes(Role role);
}
```

The `Role` interface will be the root of any `CommunityRoleType` and `PostRoleType` value as well. The `includes` method tells us whether the supplied role equals to the current one or contains it in its children. 

Look at the modified `PostRoleType` code below.

```java
public enum PostRoleType implements Role {
    VIEWER, EDITOR, REPORTER;

    private final Set<Role> children = new HashSet<>();

    static {
        REPORTER.children.add(VIEWER);
        EDITOR.children.add(VIEWER);
    }

    @Override
    public boolean includes(Role role) {
        return this.equals(role) || children.stream().anyMatch(r -> r.includes(role));
    }
}
```

We store the children of the particular role in the regular Java `HashSet` as `private final` field. What intriguing is how these children appear. By default, the set is empty for every enum value. But [the static initializer block](https://www.baeldung.com/java-static-instance-initializer-blocks) comes into play. You can treat it as the two-phase constructor. On this block, we just need to assign proper children to the required parents. The `includes` method is also rather simple. If the passed role equals to the current one, return `true`. Otherwise, perform the check recursively for every present child node.

An approach for the `CommunityRoleType` is similar. Look at the code example below.

```java
public enum CommunityRoleType implements Role {
    ADMIN, MODERATOR;

    private final Set<Role> children = new HashSet<>();

    static {
        ADMIN.children.add(MODERATOR);
        MODERATOR.children.addAll(List.of(PostRoleType.EDITOR, PostRoleType.REPORTER));
    }

    @Override
    public boolean includes(Role role) {
        return this.equals(role) || children.stream().anyMatch(r -> r.includes(role));
    }
}
```

As you can see, the `MODERATOR` role has two children: the `PostRoleType.EDITOR` and the `PostRoleType.REPORTER`. Due to the fact that both `CommunityRoleType` and the `PostRoleType` share the same interface, they can all be part of the one inheritance hierarchy.

A slight detail remains. We need to know the root of the hierarchy to perform the access validation. The easiest way is just declaring a static method that returns the required nodes. Look at the updated `Role` interface definition below.

```java
public interface Role {
    boolean includes(Role role);

    static Set<Role> roots() {
        return Set.of(CommunityRoleType.ADMIN);
    }
}
```

> I return the `Set<Role>` instead of `Role` because theoretically there might be [several roots](https://en.wikipedia.org/wiki/Polytree#:~:text=In%20mathematics%2C%20and%20more%20specifically,undirected%20graph%20is%20a%20tree.). So, it's unnecessary to restrict the roots count to 1 on the method signature. 

> Some of you may ask why not just using [Spring Security Role Hierarchy](https://www.baeldung.com/role-and-privilege-for-spring-security-registration) component? After all, it's an out-of-box solution. It suites well for the plain role model which is not the case in our context. I'll reference this point again later in the article.

## Unit testing roles inheritance

Let's test our role hierarchy. Firstly, we need to check that there are no cycles which can cause [StackOverflowError](https://www.baeldung.com/java-stack-overflow-error). Look at the test below.

```java
@Test
void shouldNotThrowStackOverflowException() {
    final var roots = Role.roots();
    final var existingRoles = Stream.concat(
        stream(PostRoleType.values()),
        stream(CommunityRoleType.values())
    ).toList();

    assertDoesNotThrow(
        () -> {
            for (Role root : roots) {
                for (var roleToCheck : existingRoles) {
                    root.includes(roleToCheck);
                }
            }
        }
    );
}
```

The idea is checking all roots and all existing roles for `includes` combinations. None of them should throw any exception.

Next move is validating the inheritance. Here are the test cases:

1. The `CommunityRoleType.ADMIN` should include any other role and the `CommunityRoleType.ADMIN` itself.
1. The `CommunityRoleType.MODERATOR` should include the `PostRoleType.EDITOR`, `PostRoleType.REPORTER`, `PostRoleType.VIEWER` and the `CommunityRoleType.MODERATOR`
1. The `PostRoleType.VIEWER` should **not** include the `PostRoleType.REPORTER`.
1. The `CommunityRoleType.MODERATOR` should **not** include the `CommunityRoleType.ADMIN`. 

Look at the code example below to see the described test suites.

```java
@ParameterizedTest
@MethodSource("provideArgs")
void shouldIncludeOrNotTheGivenRoles(Role root, Set<Role> rolesToCheck, boolean shouldInclude) {
    for (Role role : rolesToCheck) {
        assertEquals(
            shouldInclude,
            root.includes(role)
        );
    }
}

private static Stream<Arguments> provideArgs() {
    return Stream.of(
        arguments(
            CommunityRoleType.ADMIN,
            Stream.concat(
                stream(PostRoleType.values()),
                stream(CommunityRoleType.values())
            ).collect(Collectors.toSet()),
            true
        ),
        arguments(
            CommunityRoleType.MODERATOR,
            Set.of(PostRoleType.EDITOR, PostRoleType.VIEWER, PostRoleType.REPORTER, CommunityRoleType.MODERATOR),
            true
        ),
        arguments(
            PostRoleType.VIEWER,
            Set.of(PostRoleType.REPORTER),
            false
        ),
        arguments(
            CommunityRoleType.MODERATOR,
            Set.of(CommunityRoleType.ADMIN),
            false
        )
    );
}
```

> There are much more cases to cover. But I omit them for brevity.

## Defining JPA entities

You can find all the [JPA entities declarations here](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example/tree/master/src/main/java/com/example/demo/domain) and [the corresponding Flyway migrations here](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example/tree/master/src/main/resources/db/migration).

Anyway, I'm showing you the core artefacts of the system. Look at the `PostRole` and the `CommunityRole` JPA entities' declaration below.

```java
@Entity
@Table(name = "community_role")
public class CommunityRole {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "community_id")
    private Community community;

    @Enumerated(STRING)
    private CommunityRoleType type;
}

@Entity
@Table(name = "post_role")
public class PostRole {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    @Enumerated(STRING)
    private PostRoleType type;
}
```

As I've already pointed out, the `CommunityRole` binds to the `User` and the particular `Community` whilst the `PostRole` binds to the `User` as well and the specific `Post`. Therefore, the role model structure is not flat. It brings us some complexities with Spring Security. But don't worry, I'll show you how to nail them.

> Because of the vertical role model the [Spring Security Role Hierarchy](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/access/hierarchicalroles/RoleHierarchy.html) is not going to work. We need a more complicated approach. So, let’s move forward.

Look at the required SQL migrations' set (I'm using PostgreSQL) below.

```sql
CREATE TABLE community_role
(
    id           BIGSERIAL PRIMARY KEY,
    user_id      BIGINT REFERENCES users (id)     NOT NULL,
    community_id BIGINT REFERENCES community (id) NOT NULL,
    type         VARCHAR(50)                      NOT NULL,
    UNIQUE (user_id, community_id, type)
);

CREATE TABLE post_role
(
    id      BIGSERIAL PRIMARY KEY,
    user_id BIGINT REFERENCES users (id) NOT NULL,
    post_id BIGINT REFERENCES post (id)  NOT NULL,
    type    VARCHAR(50)                  NOT NULL,
    UNIQUE (user_id, post_id, type)
);
```

## Creating custom Authentication implementation

To begin with, we need to create custom `Authentication` interface implementation. Look at the code snippet below.

```java
@RequiredArgsConstructor
public class PlainAuthentication implements Authentication {
    private final Long userId;
    private volatile boolean authenticated = true;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return emptySet();
    }

    @Override
    public Long getPrincipal() {
        return userId;
    }

    @Override
    public String getName() {
        return "";
    }

    @Override
    public boolean isAuthenticated() {
        return authenticated;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        authenticated = isAuthenticated;
    }

    @Override
    public Object getCredentials() {
        return "";
    }

    @Override
    public Object getDetails() {
        return null;
    }
}
```

### Why does getAuthorities() return empty set?

The first thing to notice is that the `getAuthorities()` method always returns empty collection. Why is that? The Spring Security role model is flat, but we deal with a vertical one. If you want to put the community and the post roles belonging to the user, it may look like this:

1. `CommunityRole_ADMIN_234`
1. `PostRole_VIEWER_896`

There are three parts:

1. The type of the role
1. The role value
1. The id of the community or the post that the role references to.

In such setup, the role checking mechanism also become cumbersome. Look at the `@PreAuthorize` annotation usage example below.

```java
@PreAuthorize("hasAuthority('PostRole_VIEWER_' + #postId)")
@GetMapping("/api/post/{postId}")
public PostResponse getPost(@PathVariable Long postId) { ... }
```

In my view, this code ~~is awful~~ smells. Not only there are string typings that may cause hard tracking bugs, we lost the inheritance idea. Do you remember that the `CommunityRole.ADMIN` also includes the `PostRole.VIEWER`? But here we check only the particular authority (Spring Security just calls the `Collection.contains` method). Meaning that the `Authentication.getAuthorities()` method has to contain **all** the children's roles as well. Therefore, you have to perform these steps:

1. Select all the community roles and the post roles as well that the user possess
2. Loop through each role down to its children and put each occurred node to another collection
3. Eliminate the duplicates (or just use the `HashSet`) and return the result as the user authorities.

Not to mention the code complexity, there are also performance drawbacks. Every request comes with selecting all the user's roles from the database. But what if the user is an admin of the community? It's unnecessary to query post roles because they already have an ultimate access. But you have to do it to make the `@PreAuthorize` annotation work as expected.

I thing it's clear now why I return empty collection of authorities. Later I'm explaining how to deal with that properly.

### UserId, and volatile authenticated flag

Look at the `PlainAuthentication` below. I leave only the `getPrincipal`, and `isAuthenticated/setAuthenticated` methods to discuss.

```java
@RequiredArgsConstructor
public class PlainAuthentication implements Authentication {
    private final Long userId;
    private volatile boolean authenticated = true;

    @Override
    public Long getPrincipal() {
        return userId;
    }

    @Override
    public boolean isAuthenticated() {
        return authenticated;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        authenticated = isAuthenticated;
    }

    ...
}
```

The `userId` points to the database row containing information about the user. We’ll use it later to retrieve the roles. The `isAuthenticated/setAuthenticated` methods are part of the Spring Security contract. So, we have to implement them properly. I put [volatile](https://www.baeldung.com/java-volatile) marker because the `PlainAuthentication` object is mutable and multiple threads can access it. Better safe than sorry.

> The `getPrincipal` method inherits from the `Authentication` interface and returns `Object`. However, Java allows to return the subclass of the signature definition, if you extend the base class or implement the interface. Therefore, make your code more secure and maintainable.

> The `Authentication` interface dictates to implement 3 other methods I haven't spoken about. Those are `getName`, `getCredentials`, and `getDetails`. We use none of them later. Therefore, it's OK to return default values.

## Creating custom AuthenticationProvider

In the beginning, we should provide custom `AuthenticationProvider` to resolve the `PlainAuthentication` declared previously from the user input. Look at the code snippet below.

```java
@Component
@RequiredArgsConstructor
class DbAuthenticationProvider implements AuthenticationProvider {
    private final UserRepository userRepository;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        final var password = authentication.getCredentials().toString();
        if (!"password".equals(password)) {
            throw new AuthenticationServiceException("Invalid username or password");
        }
        return userRepository.findByName(authentication.getName())
                   .map(user -> new PlainAuthentication(user.getId()))
                   .orElseThrow(() -> new AuthenticationServiceException("Invalid username or password"));
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }
}
```

Of course, it's not a real production implementation. For the sake of simplicity, all users have the same password. If the user is present by the provided name, return its id as a `PlainAuthentication` wrapper. Otherwise, throw `AuthenticationServiceException` that will transform to the `401` status code afterwards.

## Defining Spring Security config

Finally, time to add the Spring Security config. Here I’m using [basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). Anyway, the role checking patterns I’m describing later remain the same for different authentication mechanisms. Look at the code example below.

```java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    @Bean
    @SneakyThrows
    public SecurityFilterChain securityFilterChain(HttpSecurity http) {
        return http
                   .csrf().disable()
                   .cors().disable()
                   .authorizeHttpRequests(customizer -> customizer.anyRequest().authenticated())

                   .httpBasic()
                   .authenticationEntryPoint((request, response, authException) -> response.sendError(401))
                   .and()

                   .build();
    }
}
```

## Declaring REST API methods

The described domain can provide lots of possible operations. Though I’m going to list just 4 of them. That is sufficient to cover the whole role checking idea of mine. Those are:

1. Create new community.
1. Create post for the given community.
1. Update the post by id.
1. Get post by id.

Look at the code snippet below.

```java
@RestController
@RequestMapping("/api")
public class Controller {
    @PostMapping("/community")
    @PreAuthorize("isAuthenticated()")
    public CommunityResponse createCommunity(@RequestParam String name) { ... }

    @PostMapping("/community/{communityId}/post")
    // Must have CommunityRoleType.MODERATOR role
    public PostResponse createPost(@PathVariable Long communityId, @RequestParam String name) { ... }

    @PutMapping("/post/{postId}")
    // Must have PostRoleType.EDITOR
    public void updatePost(@PathVariable Long postId, @RequestParam String name) { ... }

    @GetMapping("/post/{postId}")
    // Must have PostRoleType.VIEWER role
    public PostResponse getPost(@PathVariable Long postId) { ... }
}
```

As you can see, every user can create a new community (the creator automatically becomes an admin of the entity as well). However, I haven't implemented the required checks on the endpoints yet. At first we need the role checking service.

## Creating custom role checking service

Look at the blueprint of the `RoleService` below.

```java
@Service("RoleService")
public class RoleService {
    public boolean hasAnyRoleByCommunityId(Long communityId, Role... roles) { ... }

    public boolean hasAnyRoleByPostId(Long postId, Role... roles) { ... }
}
```

> I set the bean name inside the `@Service` annotation manually. Soon I'll explain the idea to you.

There are only two methods. The first one checks the required role presence by the `communityId` and the second one by the `postId`.

Look at the `hasAnyRoleByCommunityId` implementation below. The other method has the same idea and you can check out the whole class by [this link](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example/blob/master/src/main/java/com/example/demo/security/RoleService.java).

```java
@Service("RoleService")
@RequiredArgsConstructor
public class RoleService {
    private final CommunityRoleRepository communityRoleRepository;
    private final PostRoleRepository postRoleRepository;

    @Transactional
    public boolean hasAnyRoleByCommunityId(Long communityId, Role... roles) {
        final Long userId = ((PlainAuthentication) SecurityContextHolder.getContext().getAuthentication()).getPrincipal();
        final Set<CommunityRoleType> communityRoleTypes =
            communityRoleRepository.findRoleTypesByUserIdAndCommunityId(userId, communityId);
        for (Role role : roles) {
            if (communityRoleTypes.stream().anyMatch(communityRoleType -> communityRoleType.includes(role))) {
                return true;
            }
        }
        final Set<PostRoleType> postRoleTypes =
            postRoleRepository.findRoleTypesByUserIdAndCommunityId(userId, communityId);
        for (Role role : roles) {
            if (postRoleTypes.stream().anyMatch(postRoleType -> postRoleType.includes(role))) {
                return true;
            }
        }
        return false;
    }

    ...
}
```

Here is the algorithm:

1. Get the current user `Authentication` by calling `SecurityContextHolder.getContext().getAuthentication()` and cast it to the `PlainAuthentication` because that's the only type our application working with.
1. Find all community roles by the `userId` and the `communityId`. If any of the passed roles (according to the role inheritance model) are present in the set of the found community roles, return `true`. Otherwise, go to the next step.
1. Find all post roles by the `userId` and the `communityId`. If any of the passed roles (according to the role inheritance model) are present in the set of the found post roles, return `true`. Otherwise, return `false`.

> I also want to point out the performance benefits of the solution I'm proposing to you. The classic approach with retrieving all user authorities and calling further the `Authentication.getAuthorities` method requires us to pull every `CommunityRole` and each `PostRole` the user has from the database at once (note that the user might be a member of dozens of communities and posses hundreds of roles). But we do it much more efficiently:
1. Pull the community roles **only** for the provided combination of `(userId, communityId)`.
1. Pull the post roles **only** for the provided combination of `(userId, communityId)`.

> If the first step succeeds, the method returns `true` and doesn't perform another database query.

## Combining PreAuthorize and custom role checking service

Finally, we set almost everything and we’re ready to apply the role checking mechanism at the endpoints. Why almost? Look at the code snippet below and try to notice an improvement slot. 

```java
@RestController
@RequestMapping("/api")
public class Controller {
    @PostMapping("/community")
    @PreAuthorize("isAuthenticated()")
    public CommunityResponse createCommunity(@RequestParam String name) { ... }

    @PostMapping("/community/{communityId}/post")
    @PreAuthorize("@RoleService.hasAnyRoleByCommunityId(#communityId, T(com.example.demo.domain.CommunityRoleType).MODERATOR)")
    public PostResponse createPost(@PathVariable Long communityId, @RequestParam String name) { ... }

    @PutMapping("/post/{postId}")
    @PreAuthorize("@RoleService.hasAnyRoleByPostId(#postId, T(com.example.demo.domain.PostRoleType).EDITOR)")
    public void updatePost(@PathVariable Long postId, @RequestParam String name) { ... }

    @GetMapping("/post/{postId}")
    @PreAuthorize("@RoleService.hasAnyRoleByPostId(#postId, T(com.example.demo.domain.PostRoleType).VIEWER)")
    public PostResponse getPost(@PathVariable Long postId) { ... }
}
```

The `@RoleService` is the reference to the `RoleService` Spring bean by its name. Then I call the specific role checking method. The `postId` and the `communityId` are the method arguments and we have to prefix their usage with hash. The last parameters are _varargs_ of the required roles. Because the `Role` interface implementations are enums, we can reference the values by their fully qualified names.

As you have already guessed, this statement `T(com.example.demo.domain.CommunityRoleType).MODERATOR` has 2 problems:

1. It makes code harder to read and provokes copy-paste development.
1. If you change the package name of any enum role, then you have to update the dependent API methods accordingly.

## Short and elegant enum references in SpEL expressions

Thankfully, there is more concise solution. Look at the fixed `CommunityRoleType` definition below.

```java
public enum CommunityRoleType implements Role {
    ADMIN, MODERATOR;

    ...

    @Component("CommunityRole")
    @Getter
    static class SpringComponent {
        private final CommunityRoleType ADMIN = CommunityRoleType.ADMIN;
        private final CommunityRoleType MODERATOR = CommunityRoleType.MODERATOR;
    }
}
```

You just need to create another Spring bean that encapsulates the enum values as fields. Then you can reference them the same way we did with the `RoleService`.

> The `PostRoleType` enhancing is similar. You can check out the code by [this link](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example/blob/master/src/main/java/com/example/demo/domain/PostRoleType.java).

Let's refactor the API methods a bit and see the final result. Look at the fixed `Controller` below.

```java
@RestController
@RequestMapping("/api")
public class Controller {
    @PostMapping("/community")
    @PreAuthorize("isAuthenticated()")
    public CommunityResponse createCommunity(@RequestParam String name) { ... }

    @PostMapping("/community/{communityId}/post")
    @PreAuthorize("@RoleService.hasAnyRoleByCommunityId(#communityId, @CommunityRole.ADMIN)")
    public PostResponse createPost(@PathVariable Long communityId, @RequestParam String name) { ... }

    @PutMapping("/post/{postId}")
    @PreAuthorize("@RoleService.hasAnyRoleByPostId(#postId, @PostRole.EDITOR)")
    public void updatePost(@PathVariable Long postId, @RequestParam String name) { ... }

    @GetMapping("/post/{postId}")
    @PreAuthorize("@RoleService.hasAnyRoleByPostId(#postId, @PostRole.VIEWER)")
    public PostResponse getPost(@PathVariable Long postId) { ... }
}
```

Much more elegant solution, don’t you think? The role checking is declarative now and even non-technical folks can understand it (maybe you wish to generate documentation about endpoints restrictions' rules).

## Integration testing and validating security

If there are no tests, you cannot be sure that your code is working at all. So, let's write some. I'm going to verify these cases:

1. If a user is not authenticated, creating a new community request should return `401`.
1. If a user is authenticated, they should create a new community and new post inside it successfully.
1. If a user is authenticated but he has no rights to view the post, the request should return `403`

> I'm using [Testcontainers](https://www.testcontainers.org/modules/databases/postgres/) to start PostgreSQL during tests. The explaining of its setup is out of the scope of this article. Anyway, you can check out the whole test suite by [this link](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example/blob/master/src/test/java/com/example/demo/controller/ControllerTest.java).

Look at the unauthorized request checking below.

```java
@Test
void shouldReturn401IfUnauthorizedUserTryingToCreateCommunity() {
    userRepository.save(User.newUser("john"));

    final var communityCreatedResponse =
        rest.postForEntity(
            "/api/community?name={name}",
            null,
            CommunityResponse.class,
            Map.of("name", "community_name")
        );

    assertEquals(UNAUTHORIZED, communityCreatedResponse.getStatusCode());
}
```

The test works as expected.

![Unauthorized user check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfh40h8oaye8qxoz109s.png)

Nothing complicated here. Let’s move forward to checking community and post successful creation. Look at the code snippet down below.

```java
@Test
void shouldCreateCommunityAndPostSuccessfully() {
    userRepository.save(User.newUser("john"));

    final var communityCreatedResponse =
        rest.withBasicAuth("john", "password")
            .postForEntity(
                "/api/community?name={name}",
                null,
                CommunityResponse.class,
                Map.of("name", "community_name")
            );
    assertTrue(communityCreatedResponse.getStatusCode().is2xxSuccessful());
    final var communityId = communityCreatedResponse.getBody().id();

    final var postCreatedResponse =
        rest.withBasicAuth("john", "password")
            .postForEntity(
                "/api/community/{communityId}/post?name={name}",
                null,
                PostResponse.class,
                Map.of("communityId", communityId, "name", "post_name")
            );
    assertTrue(postCreatedResponse.getStatusCode().is2xxSuccessful());
}
```

The `john` user creates a new community and then adds a new post for it. Again, everything works smoothly.

![Create new community and post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epc2qcz1rs0waauitn40.png)

Let's get to the final case. If user has no `PostRoleType.VIEWER` for the particular post, the request of getting it should return 403. Look at the code block below.

```java
@Test
void shouldReturn403IfUserHasNoAccessToViewThePost() {
    userRepository.save(User.newUser("john"));
    userRepository.save(User.newUser("bob"));

    // john creates new community and post inside it
    ...    

    final var postViewResponse =
        rest.withBasicAuth("bob", "password")
            .getForEntity(
                "/api/post/{postId}",
                PostResponse.class,
                Map.of("postId", postId)
            );
    assertEquals(FORBIDDEN, postViewResponse.getStatusCode());
}
```

> The creation of community and post is the same as in the previous test. So, I'm omitting these parts to focus on important details.

There are two pre-configured users: `john` and `bob`. The `john` user creates a new community and post whilst `bob` tries to get the post by its id. As long as he doesn’t possess the required privileges, the server returns `403` code. Look at the result of the test running below.

![403 test result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l44x7jhjnetb59etsjv3.png)

And the final check comes. Let’s run all tests at once to validate that their behavior is deterministic.

![All tests results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4r4tczck9h5h0ly5i1d.png)

Everything works like a charm. Splendid!

## Conclusion

As you can see, Spring Security plays well with a complex role hierarchy and inheritance model. Just a few patterns and your code shines.

That's all I wanted to tell you about applying role model with Spring Security. If you have any questions or suggestions, please leave your comments down below. If the content of the article was useful, press the like button and share the link with you friends and colleagues.

Thank you very much for reading this long piece!

## Resources

1. [The entire project setup](https://github.com/SimonHarmonicMinor/spring-security-dynamic-roles-inheritance-model-example)
1. [Java static initializer block](https://www.baeldung.com/java-static-instance-initializer-blocks)
1. [Spring Security Role Hierarchy interface](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/access/hierarchicalroles/RoleHierarchy.html)
1. [Testcontainers](https://www.testcontainers.org/modules/databases/postgres/) 
1. [Basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
1. [Authorization vs authentication](https://www.okta.com/identity-101/authentication-vs-authorization/#:~:text=What's%20the%20difference%20between%20authentication,permission%20to%20access%20a%20resource.)
1. [The polytree data structure](https://en.wikipedia.org/wiki/Polytree#:~:text=In%20mathematics%2C%20and%20more%20specifically,undirected%20graph%20is%20a%20tree.)
1. [StackOverflowError](https://www.baeldung.com/java-stack-overflow-error)
1. [Guide to the Volatile Keyword in Java](https://www.baeldung.com/java-volatile)
1. [Flyway migration tool](https://flywaydb.org/)