Authorisation with Spring Security — Part 1

Christina Kaskoura
Harbor Lab
Published in
5 min readFeb 14, 2022

One of our most important concerns in Harbor Lab is protecting the data of our users from unauthorised access. As such, a proper authorisation mechanism that is easily configurable and maintainable is a very important part of our platform. Since our back end services are built with Java and Spring Boot the logical choice for our authorisation is of course Spring Security.

Authorisation in Spring Security

Spring Security defines the notion of a principal, which is the currently logged in user. When a user authenticates successfully, the principal is stored in Spring’s security context, which is thread-bound, thus making it available to the rest of our service. An important nuance here is that the security context does not propagate by default in child threads, although this is not something that needs to worry us for now.

Providing the principal to Spring falls under the authentication umbrella and is perhaps another post for another day. What we care about in terms of authorisation though, is that the principal is an implementation of the UserDetails interface, meaning that it has, among others, the following method.

Collection<? extends GrantedAuthority> getAuthorities();

The authorities, which each application can define differently based on its business needs, are a set of privileges or access rights a user has. For us, authorities give users access to specific parts of the application. It is also important to note here that we can add any other attributes or methods we need to our principal object, which we will be able to use in our authorisation checks as we will see below.

In order to provide authorisation functionality, Spring Security uses the AccessDecisionManager , which is responsible for delegating authorisation decisions to one or more AccessDecisionVoter instances. Each voter provides a decision on whether the principal is authorised or not, based on different rules or configurations, and the manager is responsible for combining the decisions of the voters in order to reach the final consensus. Spring’s default AccessDecisionManager is affirmative based, meaning that it returns true if at least one voter returns true . More details on Spring Security’s architecture are of course available in the official Spring documentation.

Configuration

Authorisation is usually part of a service’s business logic, meaning that it makes sense to apply it on the service layer of the application, ie. within our Spring service. Ideally, we need an easy and maintainable way to define the authorisation rules that apply to each service method, which is where an annotation could come in very handy.

Enter Spring global method security. Global method security allows us to use annotations in order to apply authorisation rules on methods in any layer of a Spring application. All it needs to be enabled is adding the @EnableGlobalMethodSecurity annotation on our security configuration class. This annotation also has a few interesting attributes we can use:

  • prePostEnabled: Set to true in order to enable @PreAuthorize and @PostAuthorize annotations, which we will see below in more detail.
  • securedEnabled: Set to true in order to enable Spring’s @Secured annotation, which can be used to specify a list of user roles that should have access to the annotated method.
  • jsr250Enabled: Set to true in order to enable the JSR-250 @RolesAllowed annotation, which can be used instead of Spring’s @Secured.

We can therefore have something like the following on our security configuration class.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration

Securing service methods

The first thing we want to do is limit the access to our service methods to users having specific roles. Let’s consider as an example the following code fragment:

@Service
public class ContentService {
public Article retrieve(long articleId) {
// Returns an article
}

public Article create(String title, String content) {
// Creates a new article
}
}

Assuming we have two types of users, those with the READER role and those with the WRITER role, we want to restrict access to the create method only to writers. This can easily be done as follows.

@Service
public class ContentService {
@Secured({"READER", "WRITER"})
public Article retrieve(long articleId) {
// Returns an article
}

@Secured("ROLE_WRITER")
public Article create(String title, String content) {
// Creates a new article
}
}

With these annotations Spring will check the user’s roles before calling the method and only proceed with the method call if the user has one of the specified roles, otherwise an exception will be thrown.

Roles vs Authorities

So far we conveniently left out how roles are defined and where Spring finds them in order to check them against those included in the @Secured annotation. Spring Security expects the principal we mentioned above to have a list of granted authorities. These authorities are then used by the RoleVoter, which gets all authorities starting with the prefix ROLE_ and checks them against the roles defined in the @Secured annotation. This means that in the example above Spring will check for the authorities ROLE_WRITERand ROLE_READER.

This prefix can change if needed by modifying the Spring configuration as follows.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("PREFIX_");
}
}

Authorisation based on authorities

In our case, we have decided to use authorities instead of roles since they are a better fit for our authorisation model. This means that we cannot use the @Secured annotation to check for them, but we can instead use a @PreAuthorize annotation along with the hasAuthority method as follows.

@PreAuthorize("hasAuthority('ARTICLE_VIEW')")
public Article retrieve(long articleId) {
// Returns an article
}

Similarly to how @Secured works, @PreAuthorize runs before the method is entered and allows access to the method only if the result of the evaluation is true . This means that in the case above only users who have the specified authority will be allowed to access the business functionality implemented by this method.

Come back for Part 2 of this article, where we will explore more complex authorisation rules, using SPEL and @PreAuthorize/@PostAuthorize annotations, as well as how to make sure that our transactions roll back when authorisation checks fail.

--

--