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).
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?