This module demonstrates how to apply and test Spring Security and other basic security precautions.
This showcase implements general web-level security with two different authentication methods:
-
Basic-Auth with a simple in-memory user database for the Actuator endpoints.
-
OAuth 2.0 with JWT for the service’s API.
We use two different 'scopes' - SCOPE_ACTUATOR
and SCOPE_BOOKS
- to provide users with general access.
These are 'authorities' assigned to a user inside the authorization process.
The detailed configuration is implemented in the WebSecurityConfiguration
class.
This showcase implements method-level security using the following annotations:
-
@RolesAllowed
- JEE default annotation. Uses simple 'role' names to grant access to the annotated method. Spring will be mapping the providedname
to an 'authority' with the nameROLE_$name
! -
@PreAuthorize
- Powerful Spring annotation. Allows us to use Spring Expression Language to define the criteria needed to be granted access to a method. This includes accessing method parameters and including their values into the expression. -
@Secured
- old school Java 5 annotation. Uses its value as the name of an 'authority' that grants access to the annotated method.
There is also the less commonly used @PostAuthorize
annotation.
Like @PreAuthorization
it allows for more complex rules.
The difference being that it will be evaluated after the method was executed.
This allows the expressions to access return values.
But it is not useful in preventing unauthorized execution of code.
The main use is in preventing data leakage, not actions.
The method-level security is configured in the MethodSecurityConfiguration
class and used in the BookCollection
component.
The most useful feature of Spring Security - for integration testing - is the @WithMockUser
annotation.
It allows tests to declare the characteristics of an authenticated user for the scope of the annotated test / class.
This is used for method-level (see BookCollectionTests) as well as web-level (see BooksRestControllerTests) security.
In addition to incorporating security tests with you usual functional testing, there should always be a more general security test for your web-level security rules. This is implemented in WebSecurityConfigurationTests as an example.
Note
|
Be aware that Spring Actuator does have web endpoints, but they are not part of the usual WebMVC or WebFlux technology stack.
Therefore, they cannot be tested using mid-level integration slices like |
You can - and should - include security tests in your general smoke testing efforts.
See ApplicationSecurityTests as an example.
Note that you do not need duplicated testing effort.
If you test all of your web-level configuration security rules in an application-level test (like a general smoke test), you do not need a separate @WebMvcTest
based integration test for the WebSecurityConfiguration
!
One important factor in establishing a basic application security level is to never trust data that is provided externally. We’ll refer to this as input validation. The basic principle is easy. Validate all data that comes into your application.
This includes request bodies, query and path parameters, headers etc. - everything your application actually reads and uses in some way. It is also not limited to HTTP APIs. If you consume events or other types of messages, the same principle applies.
In this showcase input validation is done using simple domain types for everything:
Be it the request body to create a new book or to query for existing books using an ISBN (see BooksRestController).
These domain types are self-validating, meaning an instance of them cannot be created without validating the data.
Providing invalid data (wrong format, too long, etc.) will fail to convert / de-serialize the data and result in a 400 Bad Request
response by the framework (see BooksRestControllerTests).
Note
|
An alternative approach would be to activate and use JEE validation annotations on parameters and request bodies. The reason we are using domain types comes down (mostly) to preference. Domain types can be used throughout the application’s code and guarantee that only valid values can be used anywhere. This improves type-safety, reduces code that checks for formats or constraints and leads to better test example data as well. Annotation-driven validation however needs to be actively triggered and does nothing to distinguish one |