4.Authorization

In this part of the workshop we want to add our customized authorization rules for our application.

As a result of the previous workshop steps we now have authentication for all our web endpoints (including the actuator endpoints) and we can log in using our own users. But here security does not stop.

We know who is using our application (authentication) but we do not have control over what this user is allowed to do in our application (authorization).

As a best practice the authorization should always be implemented on different layers like the web and method layer. This way the authorization still prohibits access even if a user manages to bypass the web url based authorization filter by playing around with manipulated URL's.

Authorization Rules

Our required authorization rule matrix looks like this:

URL

Http method

Restricted

Roles with access

/.css,/.jpg,/*.ico,...

All

No

--

/books

GET

Yes

Authenticated

/books

POST,PUT,DELETE

Yes

LIBRARY_CURATOR

/books/{id}/borrow

POST

Yes

LIBRARY_USER

/books/{id}/return

POST

Yes

LIBRARY_USER

/users

GET,POST,PUT,DELETE

Yes

LIBRARY_ADMIN

/actuator/*

GET

Yes

LIBRARY_ADMIN

Web Layer Authorizations

All the web layer authorization rules are configured in the WebSecurityConfiguration class by adding more authorization rules:

package com.example.libraryserver.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.HashMap;
import java.util.Map;

import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;
import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfiguration {

  //...

  @Configuration
  public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    public ApiWebSecurityConfigurationAdapter(
        @Qualifier("library-user-details-service") UserDetailsService userDetailsService) {
      this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      // http.csrf().disable();
      http.authorizeRequests(
              authorizeRequests ->
                  authorizeRequests
                      .requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class))
                      .permitAll()
                      .requestMatchers(EndpointRequest.toAnyEndpoint())
                      .hasRole("LIBRARY_ACTUATOR")
                      .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                      .permitAll()
                      .mvcMatchers("/")
                      .permitAll()
                      .mvcMatchers(
                          POST,
                          "/books/{bookIdentifier}/borrow/{userIdentifier}",
                          "/books/{bookIdentifier}/return/{userIdentifier}")
                      .hasRole("LIBRARY_USER")
                      .mvcMatchers(POST, "/books")
                      .hasRole("LIBRARY_CURATOR")
                      .mvcMatchers(PUT, "/books/{bookIdentifier}")
                      .hasRole("LIBRARY_CURATOR")
                      .mvcMatchers(DELETE, "/books/{bookIdentifier}")
                      .hasRole("LIBRARY_CURATOR")
                      .mvcMatchers("/users", "/users/{userIdentifier}")
                      .hasRole("LIBRARY_ADMIN")
                      .anyRequest()
                      .authenticated())
          .httpBasic(withDefaults())
          .formLogin(withDefaults())
          .headers(h -> h.httpStrictTransportSecurity().disable())
          .x509(
              x -> {
                x.subjectPrincipalRegex("CN=(.*?),");
                x.userDetailsService(userDetailsService);
              });
    }
  }
}

WebSecurityConfiguration.java

By adding the annotation @EnableGlobalMethodSecurity(prePostEnabled = true) the authorization on method layer is enabled. The web layer authorization rules are added as combinations of mvcMatchers() and hasRole() statements.

Method Layer Authorization

We continue with authorization on the method layer by adding the rules to our business service classes BookService and UserService. To achieve this we use the @PreAuthorize annotations provided by spring security. Same as other spring annotations (e.g. @Transactional) you can put @PreAuthorize annotations on global class level or on method level.

Depending on your authorization model you may use @PreAuthorize to authorize using static roles or to authorize using dynamic expressions (usually if you have roles with permissions).

LogoutForm Roles and Permissions

If you want to have a permission based authorization you can use the predefined interface PermissionEvaluator inside the @PreAuthorize annotations like this:

PermissionEvaluator

In the workshop due to time constraints we have to keep things simple so we just use static roles. Here it is done for the all operations of the book service.

BookService.java

And now we add it the same way for the all operations of the user service.

UserService.java

In this workshop lab we added the authorization to web and method layers. So now for particular RESTful endpoints access is only permitted to users with special roles.

NOTE: You find the completed code in project lab4/library-server-complete.

But how do you know that you have implemented all the authorization rules and did not leave a big security leak for your RESTful API? Or you may change some authorizations later by accident?

To be on a safer side here you need automatic testing. Yes, this can also be done for security! We will see how this works in the next workshop lab.

Last updated

Was this helpful?