mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

Posts tagged with Spring

  • Sunday, 30 May, 2021

    Providing useful API error messages with Spring Boot

    For API users it is quite important an API provides useful error messages. Otherwise, it can be hard to figure out why things do not work. Debugging what's wrong can quickly become a larger effort for the client than actually implementing useful error responses on the server side. This is especially true if clients are not able to solve the problem themself and additional communication is required.

    Nonetheless this topic is often ignored or implemented halfheartedly.

    Client and security perspectives

    There are different perspectives on error messages. Detailed error messages are more helpful for clients while, from a security perspective, it is preferable to expose as little information as possible. Luckily those two views often do not conflict that much, when implemented correctly.

    Clients are usually interested in very specific error messages if the error is produced by them. This should usually be indicated by a 4xx status code. Here, we need specific messages that point to the mistake made by the client without exposing any internal implementation detail.

    On the other hand, if the client request is valid and the error is produced by the server (5xx status codes), we should be conservative with error messages. In this case, the client is not able to solve the problem and therefore does not require any details about the error.

    A response indicating an error should contain at least two things: A human readable message and an error code. The first one helps the developer that sees the error message in the log file. The later allows specfic error processing on the client (e.g. showing a specific error message to the user).

    How to build a useful error response in a Spring Boot application?

    Assume we have a small application in which we can publish articles. A simple Spring controller to do this might look like this:

    @RestController
    public class ArticleController {
    
        @Autowired
        private ArticleService articleService;
    
        @PostMapping("/articles/{id}/publish")
        public void publishArticle(@PathVariable ArticleId id) {
            articleService.publishArticle(id);
        }
    }

    Nothing special here, the controller just delegates the operation to a service, which looks like this:

    @Service
    public class ArticleService {
    
        @Autowired
        private ArticleRepository articleRepository;
    
        public void publishArticle(ArticleId id) {
            Article article = articleRepository.findById(id)
                    .orElseThrow(() -> new ArticleNotFoundException(id));
    
            if (!article.isApproved()) {
                throw new ArticleNotApprovedException(article);
            }
    
            ...
        }
    }

    Inside the service we throw specific exceptions for possible client errors. Note that those exception do not just describe the error. They also carry information that might help us later to produce a good error message:

    public class ArticleNotFoundException extends RuntimeException {
        private final ArticleId articleId;
    
        public ArticleNotFoundException(ArticleId articleId) {
            super(String.format("No article with id %s found", articleId));
            this.articleId = articleId;
        }
        
        // getter
    }

    If the exception is specific enough we do not need a generic message parameter. Instead, we can define the message inside the exception constructor.

    Next we can use an @ExceptionHandler method in a @ControllerAdvice bean to handle the actual exception:

    @ControllerAdvice
    public class ArticleExceptionHandler {
    
        @ExceptionHandler(ArticleNotFoundException.class)
        public ResponseEntity<ErrorResponse> onArticleNotFoundException(ArticleNotFoundException e) {
            String message = String.format("No article with id %s found", e.getArticleId());
            return ResponseEntity
                    .status(HttpStatus.NOT_FOUND)
                    .body(new ErrorResponse("ARTICLE_NOT_FOUND", message));
        }
        
        ...
    }

    If controller methods throw exceptions, Spring tries to find a method annotated with a matching @ExceptionHandler annotation. @ExceptionHandler methods can have flexible method signatures, similar to standard controller methods. For example, we can a HttpServletRequest request parameter and Spring will pass in the current request object. Possible parameters and return types are described in the Javadocs of @ExceptionHandler.

    In this example, we create a simple ErrorResponse object that consists of an error code and a message.

    The message is constructed based on the data carried by the exception. It is also possible to pass the exception message to the client. However, in this case we need to make sure everyone in the team is aware of this and exception messages do not contain sensitive information. Otherwise, we might accidentally leak internal information to the client.

    ErrorResponse is a simple Pojo used for JSON serialization:

    public class ErrorResponse {
        private final String code;
        private final String message;
    
        public ErrorResponse(String code, String message) {
            this.code = code;
            this.message = message;
        }
    
        // getter
    }

    Testing error responses

    A good test suite should not miss tests for specific error responses. In our example we can verify error behaviour in different ways. One way is to use a Spring MockMvc test.

    For example:

    @SpringBootTest
    @AutoConfigureMockMvc
    public class ArticleExceptionHandlerTest {
    
        @Autowired
        private MockMvc mvc;
    
        @MockBean
        private ArticleRepository articleRepository;
    
        @Test
        public void articleNotFound() throws Exception {
            when(articleRepository.findById(new ArticleId("123"))).thenReturn(Optional.empty());
    
            mvc.perform(post("/articles/123/publish"))
                    .andExpect(status().isNotFound())
                    .andExpect(jsonPath("$.code").value("ARTICLE_NOT_FOUND"))
                    .andExpect(jsonPath("$.message").value("No article with id 123 found"));
        }
    }


    Here, we use a mocked ArticleRepository that returns an empty Optional for the passed id. We then verify if the error code and message match the expected strings.

    In case you want to learn more about testing spring applications with mock mvc: I recently wrote an article showing how to improve Mock mvc tests.

    Summary

    Useful error message are an important part of an API.

    If errors are produced by the client (HTTP 4xx status codes) servers should provide a descriptive error response containing at least an error code and a human readable error message. Responses for unexpected server errors (HTTP 5xx) should be conservative to avoid accidental exposure any internal information.

    To provide useful error responses we can use specific exceptions that carry related data. Within @ExceptionHandler methods we then construct error messages based on the exception data.

  • Tuesday, 2 February, 2021

    Validation in Spring Boot applications

    Validation in Spring Boot applications can be done in many different ways. Depending on your requirements some ways might fit better to your application than others. In this post we will explore the usual options to validate data in Spring Boot applications.

    Validation is done by using the Bean Validation API. The reference implementation for the Bean Validation API is Hibernate Validator.

    All required dependencies are packaged in the Spring Boot starter POM spring-boot-starter-validation. So usually all you need to get started is the following dependency:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    

    Validation constraints are defined by annotating fields with appropriate Bean Validation annotations. For example:

    public class Address {
    
        @NotBlank
        @Size(max = 50)
        private String street;
    
        @NotBlank
        @Size(max = 50)
        private String city;
    
        @NotBlank
        @Size(max = 10)
        private String zipCode;
        
        @NotBlank
        @Size(max = 3)
        private String countryCOde;
    
        // getters + setters
    }

    I think these annotations are quite self-explanatory. We will use this Address class in many of the following examples.

    You can find a complete list of build in constraint annotations in the Bean Validation documentation. Of course you can also define you own validation constraints by creating a custom ConstraintValidator.

    Defining validation constraints is only one part. Next we need to trigger the actual validation. This can be done by Spring or by manually invoking a Validator. We will see both approaches in the next sections.

    Validating incoming request data

    When building a REST API with Spring Boot it is likely you want to validate incoming request data. This can be done by simply adding the @Valid Annotation to the @RequestBody method parameter. For example:

    @RestController
    public class AddressController {
    
        @PostMapping("/address")
        public void createAddress(@Valid @RequestBody Address address) {
            // ..
        }
    }

    Spring now automatically validates the passed Address object based on the previously defined constraints.

    This type of validation is usually used to make sure the data sent by the client is syntactically correct. If the validation fails the controller method is not called and a HTTP 400 (Bad request) response is returned to the client. More complex business specific validation constraints should typically be checked later in the business layer.

    Persistence layer validation

    When using a relational database in your Spring Boot application, it is likely that you are also using Spring Data and Hibernate. Hibernate comes with supports for Bean Validation. If your entities contain Bean Validation annotations, those are automatically checked when persisting an entity.

    Note that the persistence layer should definitely not be the only location for validation. If validation fails here, it usually means that some sort of validation is missing in other application components. Persistence layer validation should be seen as the last line of defense. In addition to that, the persistence layer is usually too late for business related validation.

    Method parameter validation

    Another option is the method parameter validation provided by Spring. This allows us to add Bean Validation annotations to method parameters. Spring then uses an AOP interceptor to validate the parameters before the actual method is called.

    For example:

    @Service
    @Validated
    public class CustomerService {
    
        public void updateAddress(
                @Pattern(regexp = "\\w{2}\\d{8}") String customerId,
                @Valid Address newAddress
        ) {
            // ..
        }
    }

    This approach can be useful to validate data coming into your service layer. However, before committing to this approach you should be aware of its limitations as this type of validation only works if Spring proxies are involved. See my separate post about Method parameter validation for more details.

    Note that this approach can make unit testing harder. In order to test validation constraints in your services you now have to bootstrap a Spring application context.

    Triggering Bean Validation programmatically

    In the previous validation solutions the actual validation is triggered by Spring or Hibernate. However, it can be quite viable to trigger validation manually. This gives us great flexibility in integrating validation into the appropriate location of our application.

    We start by creating a ValidationFacade bean:

    @Component
    public class ValidationFacade {
    
        private final Validator validator;
    
        public ValidationFacade(Validator validator) {
            this.validator = validator;
        }
    
        public <T> void validate(T object, Class<?>... groups) {
            Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }
    }

    This bean accepts a Validator as constructor parameter. Validator is part of the Bean Validation API and responsible for validating Java objects. An instance of Validator is automatically provided by Spring, so it can be injected into our ValidationFacade.

    Within the validate(..) method we use the Validator to validate a passed object. The result is a Set of ConstraintViolations. If no validation constraints are violated (= the object is valid) the Set is empty. Otherwise, we throw a ConstraintViolationException.

    We can now inject our ValidationFacade into other beans. For example:

    @Service
    public class CustomerService {
    
        private final ValidationFacade validationFacade;
    
        public CustomerService(ValidationFacade validationFacade) {
            this.validationFacade = validationFacade;
        }
    
        public void updateAddress(String customerId, Address newAddress) {
            validationFacade.validate(newAddress);
            // ...
        }
    }

    To validate an object (here newAddress) we simply have to call the validate(..) method of ValidationFacade. Of course we could also inject the Validator directly in our CustomerService. However, in case of validation errors we usually do not want to deal with the returned Set of ConstraintViolations. Instead it is likely we simply want to throw an exception, which is exactly what ValidationFacade is doing.

    Often this is a good approach for validation in the service/business layer. It is not limited to method parameters and can be used with different types of objects. For example, we can load an object from the database, modify it and then validate it before we continue.

    This way is also quite good to unit test as we can simply mock ValidationFacade. In case we want real validation in unit tests, the required Validator instance can be created manually (as shown in the next section). Both cases do not require to bootstrap a Spring application context in our tests.

    Validating inside business classes

    Another approach is to move validation inside your actual business classes. When doing Domain Driven Design this can be a good fit. For example, when creating an Address instance the constructor can make sure we are not able to construct an invalid object:

    public class Address {
    
        @NotBlank
        @Size(max = 50)
        private String street;
    
        @NotBlank
        @Size(max = 50)
        private String city;
    
        ...
        
        public Address(String street, String city) {
            this.street = street;
            this.city = city;
            ValidationHelper.validate(this);
        }
    }

    Here the constructor calls a static validate(..) method to validate the object state. This static validate(..) methods looks similar to the previously shown method in ValidationFacade:

    public class ValidationHelper {
    
        private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
        public static <T> void validate(T object, Class<?>... groups) {
            Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
            if (!violations.isEmpty()) {
                throw new ConstraintViolationException(violations);
            }
        }
    }

    The difference here is that we do not retrieve the Validator instance by Spring. Instead, we create it manually by using:

    Validation.buildDefaultValidatorFactory().getValidator()

    This way we can integrate validation directly into domain objects without relying on someone outside to validate the object.

    Summary

    We saw different ways to deal with validation in Spring Boot applications. Validating incoming request data is good to reject nonsense as early as possible. Persistence layer validation should only be used as additional layer of safety. Method validation can be quite useful, but make sure you understand the limitations. Even if triggering Bean Validation programmatically takes a bit more effort, it is usually the most flexible way.

    You can find the source code for the shown examples on GitHub.

  • Monday, 2 November, 2020

    Improving Spring Mock-MVC tests

    Spring Mock-MVC can be a great way to test Spring Boot REST APIs. Mock-MVC allows us to test Spring-MVC request handling without running a real server.

    I used Mock-MVC tests in various projects and in my experience they often become quite verbose. This doesn't have to be bad. However, it often results in copy/pasting code snippets around in test classes. In this post we will look at a couple of ways to clean up Spring Mock-MVC tests.

    Decide what to test with Mock-MVC

    The first question we need to ask is what we want to test with Mock-MVC. Some example test scenarios are:

    • Testing only the web layer and mocking all controller dependencies.
    • Testing the web layer with domain logic and mocked third party dependencies like Databases or message queues.
    • Testing the complete path from web to database by replacing third party dependencies with embedded alternatives if possible (e.g. H2 or embedded-Kafka)

    All these scenarios have their own up- and downsides. However, I think there are two simple rules we should follow:

    • Test as much in standard JUnit tests (without Spring) as possible. This improves test performance a lot and makes tests often easier to write.
    • Pick the scenario(s) you want to test with Spring and be consistent in the dependencies you mock. This makes tests easier to understand and can speed them up as well. When running many different test configurations, Spring often has to re-initialize the application context which slows tests down.

    When using standard JUnit tests as much as possible the last scenario mentioned above is often a good fit. After we tested all logic with fast unit tests, we can use a few Mock-MVC tests to verify that all pieces work together, from controller to database.

    Cleaning up test configuration using custom annotations

    Spring allows us to compose multiple Spring annotations to a single custom annotation.

    For example, we can create a custom @MockMvcTest annotation:

    @SpringBootTest
    @TestPropertySource(locations = "classpath:test.properties")
    @AutoConfigureMockMvc(secure = false)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MockMvcTest {}

    Our test now only needs a single annotation:

    @MockMvcTest
    public class MyTest {
        ...
    }

    This way we can clean up tests from various annotations. This is also useful to standardize Spring configuration for our test scenarios.

    Improving Mock-MVC requests

    Let's look at the following example Mock-MVC request and see how we can improve it:

    mockMvc.perform(put("/products/42")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"Cool Gadget\", \"description\": \"Looks cool\"}")
            .header("Authorization", getBasicAuthHeader("John", "secr3t")))
            .andExpect(status().isOk());

    This sends a PUT request with some JSON data and an Authorization header to /products/42.

    The first thing that catches someone's eye is the JSON snippet within a Java string. This is obviously a problem as the double quote escaping required by Java strings makes it barely readable.

    Typically we should use an object that is then converted to JSON. Before we look into this approach, it is worth to mention Text blocks. Java Text blocks have been introduced in JDK 13 / 14 as preview feature. Text blocks are strings that span over multiple lines and require no double quote escaping.

    With text block we can format inline JSON in a prettier way. For example:

    mvc.perform(put("/products/42")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .content("""
                {
                    "name": "Cool Gadget",
                    "description": "Looks cool"
                }
                """)
            .header("Authorization", getBasicAuthHeader("John", "secr3t")))
            .andExpect(status().isOk());  

    In certain situations this can be useful.

    However, we should still prefer objects that are converted to JSON instead of manually writing and maintaining JSON strings.

    For example:

    Product product = new Product("Cool Gadget", "Looks cool");
    mvc.perform(put("/products/42")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .content(objectToJson(product))
            .header("Authorization", getBasicAuthHeader("John", "secr3t")))
            .andExpect(status().isOk());
    

    Here we create a product object and convert it to JSON with a small objectToJson(..) helper method. This helps a bit. Nevertheless, we can do better.

    Our request contains a lot of elements that can be grouped together. When building a JSON REST-API it is likely that we often have to send similar PUT request. Therefore, we create a small static shortcut method:

    public static MockHttpServletRequestBuilder putJson(String uri, Object body) {
        try {
            String json = new ObjectMapper().writeValueAsString(body);
            return put(uri)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON)
                    .content(json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    This method converts the body parameter to JSON using a Jackson ObjectMapper. It then creates a PUT request and sets Accept and Content-Type headers.

    This reusable method simplifies our test request a lot:

    Product product = new Product("Cool Gadget", "Looks cool");
    mvc.perform(putJson("/products/42", product)
            .header("Authorization", getBasicAuthHeader("John", "secr3t")))
            .andExpect(status().isOk())

    The nice thing here is that we do not lose flexibility. Our putJson(..) method returns a MockHttpServletRequestBuilder. This allows us to add additional request properties within tests if required (like the Authorization header in this example).

    Authentication headers are another topic we often have to deal with when writing Spring Mock-MVC tests. However, we should not add authentication headers to our previous putJson(..) method. Even if all PUT requests require authentication we stay more flexible if we deal with authentication in a different way.

    RequestPostProcessors can help us with this. As the name suggests, RequestPostProcessors can be used to process the request. We can use this to add custom headers or other information to the request.

    For example:

    public static RequestPostProcessor authentication() {
        return request -> {
            request.addHeader("Authorization", getBasicAuthHeader("John", "secr3t"));
            return request;
        };
    } 

    The authentication() method returns a RequestPostProcessor which adds Basic-Authentication to the request. We can apply this RequestPostProcessor in our test using the with(..) method:

    Product product = new Product("Cool Gadget", "Looks cool");
    mvc.perform(putJson("/products/42", product).with(authentication()))
            .andExpect(status().isOk())

    This does not only simplify our test request. If we change the request header format we now only need to modify a single method to fix the tests. Additionally putJson(url, data).with(authentication()) is also quite expressive to read.

    Improving response verification

    Now let's see how we can improve response verification.

    We start with the following example:

    mvc.perform(get("/products/42"))
            .andExpect(status().isOk())
            .andExpect(header().string("Cache-Control", "no-cache"))
            .andExpect(jsonPath("$.name").value("Cool Gadget"))
            .andExpect(jsonPath("$.description").value("Looks cool"));

    Here we check the HTTP status code, make sure the Cache-Control header is set to no-cache and use JSON-Path expressions to verify the response payload.

    The Cache-Control header looks like something we probably need to check for multiple responses. In this case, it can be a good idea to come up with a small shortcut method:

    public ResultMatcher noCacheHeader() {
        return header().string("Cache-Control", "no-cache");
    }

    We can now apply the check by passing noCacheHeader() to andExpect(..):

    mvc.perform(get("/products/42"))
            .andExpect(status().isOk())
            .andExpect(noCacheHeader())
            .andExpect(jsonPath("$.name").value("Cool Gadget"))
            .andExpect(jsonPath("$.description").value("Looks cool"));
    

    The same approach can be used to verify the response body.

    For example we can create a small product(..) method that compares the response JSON with a given Product object:

    public static ResultMatcher product(String prefix, Product product) {
        return ResultMatcher.matchAll(
                jsonPath(prefix + ".name").value(product.getName()),
                jsonPath(prefix + ".description").value(product.getDescription())
        );
    }

    Our test now looks like this:

    Product product = new Product("Cool Gadget", "Looks cool");
    mvc.perform(get("/products/42"))
            .andExpect(status().isOk())
            .andExpect(noCacheHeader())
            .andExpect(product("$", product));

    Note that the prefix parameter gives us flexibility. The object we want to check might not always be located at the JSON root level of the response.

    Assume a request might return a collection of products. We can then use the prefix parameter to select each product in the collection. For example:

    Product product0 = ..
    Product product1 = ..
    mvc.perform(get("/products"))
            .andExpect(status().isOk())
            .andExpect(product("$[0]", product0))
            .andExpect(product("$[1]", product1));
      

    With ResultMatcher methods you avoid scattering the exact response data structure over many tests. This again supports refactorings.

    Summary

    We looked into a few ways to reduce verbosity in Spring Mock-MVC tests. Before we even start writing Mock-MVC tests we should decide what we want to test and what parts of the application should be replaced with mocks. Often it is a good idea to test as much as possible with standard unit tests (without Spring and Mock-MVC).

    We can use custom test annotations to standardize our Spring Mock-MVC test setup. With small shortcut methods and RequestPostProcessors we can move reusable request code out of test methods. Custom ResultMatchers can be used to improve response checks.

    You can find the example code on GitHub.

  • Thursday, 15 October, 2020

    Spring Security: Delegating authorization checks to bean methods

    In this post we will learn how authorization checks can be delegated to bean methods with Spring Security. We will also learn why this can be very useful in many situations and how it improves testability of our application. Before we start, we will quickly look over common Spring Security authorization methods.

    Spring Security and authorization

    Spring Security provides multiple ways to deal with authorization. Some of them are based on user roles, others are based on more flexible expressions or custom beans. I don't want to go into details here, many articles are already available on this topic. Just to give you a quick overview, here are a few commented examples of common ways to define access rules with Spring Security:

    Restricting URL access via a WebSecurityConfigurerAdapter:

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
            
                // restrict url access based on roles
                .antMatchers("/internal/**").hasRole("ADMIN")
                .antMatchers("/projects/**").hasRole("USER")
                
                // restrict url access based on expression
                .antMatchers("/users/{username}/profile")
                    .access("principal.username == #username");            
        }
    }

    Using annotations to restrict access to methods:

    @Service
    public class SomeService {
    
        // Using Springs @Secured annotation for role checks
        @Secured("ROLE_ADMIN")
        public void doAdminStuff() { }
    
        // Using JSR 250 RolesAllowed annotation for role checks
        @RolesAllowed("ROLE_ADMIN")
        public void doOtherAdminStuff() { }
    
        // Using Springs @PreAuthorize annotation with an expression 
        @PreAuthorize("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")
        public void doMoreAdminStuff() { }
        
        // Using an expression to delegate to a PermissionEvaluator bean
        @PreAuthorize("hasPermission(#stuff, 'write')")
        public void doStuff(Stuff stuff) { }
    }

    What to use when?

    If roles are the only thing you need, it is easy. You just need to decide if you prefer defining the required roles based on URLs or based on methods in your Java code. If you prefer the later, just pick one annotation and use it consistently.

    In case you need some ACL-like security (e.g. User x has permission y on object z) using @PreAuthorize with hasPermission(..) and a custom PermissionEvaluator is often a good choice. Also, have a look at the Spring Security ACL support.

    However, there is a huge field between both approaches where roles are not enough but ACLs might be too fine grained or just the wrong tool. Here are a few example authorization rules that do not fit well into both solutions:

    Access to a resource should only be given ..

    • .. to the owner of the resource (e.g. a user can only change his own profile)
    • .. to users with role x from department y
    • .. during standard business times
    • .. to administrators who signed in using two-factor authentication
    • .. to users who connect from specific IP addresses

    All those examples can probably be solved by building a security expression and passing it to @PreAuthorize. However, in practice it is often not that simple.

    Let us look at the last example (the ip address check). The previously shown code snippet contains a @PreAuthorize example that does exactly this:

    @PreAuthorize("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")
    

    This looks nice as an example and shows what you can do with security expressions. However, now consider:

    • You possibly need to define more than one IP range. So, you have to combine multiple hasIpAddress(..) checks.
    • You probably do not want to hard-code IP addresses in your code. Instead they should be resolved from configuration properties.
    • It is likely that you need the same access check in different parts of your code. You probably do not want it to duplicate it over and over.

    In other cases you might need to do a database look-up or call another external system to decide if a user is allowed to access a resource.

    Simple expressions are fine. However, if they get larger and are scattered all over a code base they can become painful to maintain.

    Side note: Spring Security implements method security by proxying the target bean. Security checks are then added via the proxy. If you don't know about proxies, you should probably read my post about the Proxy pattern.

    Delegating access decisions to beans

    Within security expressions we can reference beans using the @beanname syntax. This feature can help us to implement the previously described authentication rules.

    Let's look at an example:

    @Service
    public class ProjectService {
    
        @PreAuthorize("@projectAccess.canUpdateProjectName(#id)")
        public void updateProjectName(int id, String newName) {
            ...
        }
        
        @PreAuthorize("@projectAccess.canDeleteProject(#id)")
        public void deleteProject(int id) {
            ...
        }
    }

    Here we define a ProjectService class with two methods, both annotated with @PreAuthorize. Within the security expression we delegate the access check to methods of a bean named projectAccess. Relevant method parameters (here id) are passed to projectAccess methods.

    projectAccess looks like this:

    @Component("projectAccess")
    public class ProjectAccessHandler {
    
        private final ProjectRepository projectRepository;
        private final AuthenticatedUserService authenticatedUserService;
    
        public ProjectAccessHandler(ProjectRepository repo, AuthenticatedUserService aus) {
            this.projectRepository = repo;
            this.authenticatedUserService = aus;
        }
    
        public boolean canUpdateProjectName(int id) {
            return isProjectOwner(id);
        }
    
        public boolean canDeleteProject(int id) {
            return isProjectOwner(id);
        }
    
        private boolean isProjectOwner(int id) {
            User user = authenticatedUserService.getAuthenticatedUser();
            Project project = projectRepository.findById(id);
            return (project.getOwner().equals(user.getUsername()));
        }
    }

    It is a simple bean with two public methods that are called via security expressions. In both cases only the owner of the project is allowed to perform the operation. To determine the project owner we first have to look-up the related project by using a ProjectRepository bean.

    The injected AuthenticatedUserService is a simple facade around Spring Security's SecurityContextHolder:

    @Service
    public class AuthenticatedUserService {
        public User getAuthenticatedUser() {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            return (User) authentication.getPrincipal();
        }
    }

    This cleans up our code a little bit because it removes Spring Security internals (and the type cast) from our access control logic. It also becomes helpful when writing unit tests. This way we do not have to deal with static method calls during tests.

    Note we use the standard Spring Security User class for simplicity in this example. Often it is a good idea to create your own customized class as principal. However, this is something for another blog post.

    Testing access rules

    Another important benefit of this approach is that we can test access rules in simple unit tests. No Spring application context is required to evaluate @PreAuthorize expressions. This speeds up tests a lot.

    A simple test for canUpdateProjectName(..) might look like this:

    public class ProjectAccessHandlerTest {
    
        private ProjectRepository repository = mock(ProjectRepository.class);
        private AuthenticatedUserService service = mock(AuthenticatedUserService.class);
        private ProjectAccessHandler accessHandler = new ProjectAccessHandler(repository, service);
        private User john = new User("John", "password", Collections.emptyList());
    
        @Test
        public void canUpdateProjectName_isOwner() {
            Project project = new Project(1, "John", "John's project");
            when(repository.findById(1)).thenReturn(project);
            when(service.getAuthenticatedUser()).thenReturn(john);
            assertTrue(accessHandler.canUpdateProjectName(1));
        }
    
        @Test
        public void canUpdateProjectName_isNotOwner() {
            Project project = new Project(1, "Anna", "Anna's project");
            when(repository.findById(1)).thenReturn(project);
            when(service.getAuthenticatedUser()).thenReturn(john);
            assertFalse(accessHandler.canUpdateProjectName(1));
        }
    }

    Summary

    Many authorization requirements cannot be solved by using roles alone and ACLs often do not fit. In those situation it can be a viable solution to create separate beans for handling access checks. With @PreAuthorize we can delegate the authorization check to those beans. This also simplifies writing tests as we do not have to create a Spring application context to test authorization constraints.

    You can find the shown example code on GitHub.

  • Wednesday, 9 September, 2020

    Quick tip: Referencing other Properties in Spring

    In Spring property (or yaml) files we can reference other properties using the ${..} syntax.

    For example:

    external.host=https://api.external.com
    external.productService=${external.host}/product-service
    external.orderService=${external.host}/order-service
    

    If we now access the external.productService property (e.g. by using the @Value annotation) we will get the value https://api.external.com/product-service.

    For example:

    @Value("${external.productService}")
    private String productServiceUrl; // https://api.external.com/product-service
    

    This way we can avoid duplication of commonly used values in property and yaml files.

  • Monday, 3 August, 2020

    Integrating JSON Schema validation in Spring using a custom HandlerMethodArgumentResolver

    In previous posts we learned about JSON Schema and how we can validate a JSON document against a JSON Schema in Java. In this post we will integrate JSON Schema validation into a Spring Boot application using a custom HandlerMethodArgumentResolver. We will use the same JSON document and JSON Schema as in previous posts.

    So, what is a HandlerMethodArgumentResolver?

    Handler methods in Spring controllers (= methods annotated with @RequestMapping, @GetMapping, etc.) have flexible method signatures. Depending on what is needed inside a controller method, various method arguments can be added. Examples are request and response objects, headers, path variables or session values. Those arguments are resolved using HandlerMethodArgumentResolvers. Based on the argument definition (type, annotations, etc.) a HandlerMethodArgumentResolver is responsible for obtaining the actual value that should be passed to the controller.

    A few standard HandlerMethodArgumentResolvers provided by Spring are:

    • PathVariableMethodArgumentResolver resolves arguments annotated with @PathVariable.
    • Request related method arguments like WebRequest, ServletRequest or MultipartRequest are resolved by ServletRequestMethodArgumentResolve.
    • Arguments annotated with @RequestHeader are resolved by RequestHeaderMapMethodArgumentResolver.

    In the following we will create our own HandlerMethodArgumentResolver implementation that validates a JSON request body against a JSON Schema before the JSON data is passed to a controller method.

    Getting started

    Like in the previously mentionend article about JSON Schema validation in Java we will use the json-schema-validator library:

    <dependency>
        <groupId>com.networknt</groupId>
        <artifactId>json-schema-validator</artifactId>
        <version>1.0.42</version>
    </dependency>

    We start with creating our own @ValidJson annotation. This annotation will be used to mark controller method arguments that should be resolved by our own HandlerMethodArgumentResolver.

    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidJson {
        String value();
    }
    

    As we see in the next snippet, the value parameter of our @ValidJson annotation is used to define the path to the JSON Schema. We can now come up with the following controller implementation:

    public class Painting {
        private String name;
        private String artist;
    
       // more fields, getters + setters
    }
    public interface SchemaLocations {
        String PAINTING = "classpath:painting-schema.json";
    }
    @RestController
    public class PaintingController {
    
        @PostMapping("/paintings")
        public ResponseEntity<Void> createPainting(@ValidJson(PAINTING) Painting painting) {
            ...
        }
    }

    Painting is a simple POJO we use for JSON mapping. SchemaLocations contains the location of our JSON Schema documents. In the handler method createPainting we added a Painting argument annotated with @ValidJson. We pass the PAINTING constant to the @ValidJson annotation to define which JSON Schema should be used for validation.

    Implementing HandlerMethodArgumentResolver

    HandlerMethodArgumentResolver is an interface with two methods:

    public interface HandlerMethodArgumentResolver {
    
        boolean supportsParameter(MethodParameter parameter);
    
        Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
    }

    supportsParameter(..) is used to check if this HandlerMethodArgumentResolver can resolve a given MethodParameter, while resolveArgument(..) resolves and returns the actual argument.

    We implement this interface in our own class: JsonSchemaValidatingArgumentResolver:

    public class JsonSchemaValidatingArgumentResolver implements HandlerMethodArgumentResolver {
    
        private final ObjectMapper objectMapper;
        private final ResourcePatternResolver resourcePatternResolver;
        private final Map<String, JsonSchema> schemaCache;
    
        public JsonSchemaValidatingArgumentResolver(ObjectMapper objectMapper, ResourcePatternResolver resourcePatternResolver) {
            this.objectMapper = objectMapper;
            this.resourcePatternResolver = resourcePatternResolver;
            this.schemaCache = new ConcurrentHashMap<>();
        }
    
        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.getParameterAnnotation(ValidJson.class) != null;
        }
        
        ...
    }

    supportsParameter(..) is quite easy to implement. We simply check if the passed MethodParameter is annotated with @ValidJson.

    In the constructor we take a Jackson ObjectMapper and a ResourcePatternResolver. We also create a ConcurrentHashMap that will be used to cache JsonSchema instances.

    Next we implement two helper methods: getJsonPayload(..) returns the JSON request body as String while getJsonSchema(..) returns a JsonSchema instance for a passed schema path.

    private String getJsonPayload(NativeWebRequest nativeWebRequest) throws IOException {
        HttpServletRequest httpServletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        return StreamUtils.copyToString(httpServletRequest.getInputStream(), StandardCharsets.UTF_8);
    }
    
    private JsonSchema getJsonSchema(String schemaPath) {
        return schemaCache.computeIfAbsent(schemaPath, path -> {
            Resource resource = resourcePatternResolver.getResource(path);
            if (!resource.exists()) {
                throw new JsonSchemaValidationException("Schema file does not exist, path: " + path);
            }
            JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
            try (InputStream schemaStream = resource.getInputStream()) {
                return schemaFactory.getSchema(schemaStream);
            } catch (Exception e) {
                throw new JsonSchemaValidationException("An error occurred while loading JSON Schema, path: " + path, e);
            }
        });
    }

    A JsonSchema is retrieved from a Spring Resource that is obtained from a ResourcePatternResolver. JsonSchema instances are cached in the previously created Map. So a JsonSchema is only loaded once. If an error occurs while loading the JSON Schema, a JsonSchemaValidationException is thrown.

    The last step is the implementation of the resolveArgument(..) method:

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        // get schema path from ValidJson annotation
        String schemaPath = methodParameter.getParameterAnnotation(ValidJson.class).value();
    
        // get JsonSchema from schemaPath
        JsonSchema schema = getJsonSchema(schemaPath);
    
        // parse json payload
        JsonNode json = objectMapper.readTree(getJsonPayload(nativeWebRequest));
    
        // Do actual validation
        Set<ValidationMessage> validationResult = schema.validate(json);
    
        if (validationResult.isEmpty()) {
            // No validation errors, convert JsonNode to method parameter type and return it
            return objectMapper.treeToValue(json, methodParameter.getParameterType());
        }
    
        // throw exception if validation failed
        throw new JsonValidationFailedException(validationResult);
    }

    Here we first get the location of the JSON Schema from the annotation and resolve it to an actual JsonSchema instance. Next we parse the request body to a JsonNode and validate it using the JsonSchema. If validation errors are present we throw a JsonValidationFailedException.

    You can find the source code for the complete class on GitHub.

    Registering our HandlerMethodArgumentResolver

    Next we need to tell Spring about our JsonSchemaValidatingArgumentResolver. We do this by using the addArgumentResolvers(..) method from the WebMvcConfigurer interface.

    @Configuration
    public class JsonValidationConfiguration implements WebMvcConfigurer {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Autowired
        private ResourcePatternResolver resourcePatternResolver;
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
            resolvers.add(new JsonSchemaValidatingArgumentResolver(objectMapper, resourcePatternResolver));
        }
    }

    Error handling

    In our JsonSchemaValidatingArgumentResolver we throw two different exceptions:

    • JsonSchemaLoadingFailedException if loading the JSON Schema fails
    • JsonValidationFailedException if the JSON validation fails

    JsonSchemaLoadingFailedException very likely indicates a programming error while a JsonValidationFailedException is caused by a client sending an invalid JSON document. So we should clearly send a useful error message to the client if the later occurs.

    We do this by using an @ExceptionHandler method in a class annotated with @ControllerAdvice:

    @ControllerAdvice
    public class JsonValidationExceptionHandler {
    
        @ExceptionHandler(JsonValidationFailedException.class)
        public ResponseEntity<Map<String, Object>> onJsonValidationFailedException(JsonValidationFailedException ex) {
            List<String> messages = ex.getValidationMessages().stream()
                    .map(ValidationMessage::getMessage)
                    .collect(Collectors.toList());
            return ResponseEntity.badRequest().body(Map.of(
                "message", "Json validation failed",
                "details", messages
            ));
        }
    }

    Within the method we retrieve the validation messages from the passed exception and send them to the client.

    Example request

    An example request we can send to our PaintingController looks like this:

    POST http://localhost:8080/paintings
    Content-Type: application/json
    
    {
      "name": "Mona Lisa",
      "artist": null,
      "description": null,
      "dimension": {
        "height": -77,
        "width": 53
      },
      "tags": [
        "oil",
        "famous"
      ]
    }

    Note that the JSON body contains two errors: artist is not allowed to be null and the painting height is negative. (You can look at the JSON Schema on GitHub)

    Our server responds to this request with the following response:

    HTTP/1.1 400 (Bad Request)
    {
      "message": "Json validation failed",
      "details": [
        "$.artist: null found, string expected",
        "$.dimension.height: must have a minimum value of 1"
      ]
    }

    Summary

    We learned how to integrate JSON Schema validation into a Spring Boot application by implementing a custom HandlerMethodArgumentResolver. Within our implementation we validate the JSON request body against a JSON Schema before it is passed as argument to the controller. To return proper error message we can add a @ExceptionHandler method.

    You can find the complete example project on GitHub.