This article is all of you who already know the basics of Spring Security. Considering you have the prerequisites, I assume you’ve already used the WebSecurityConfigurerAdapter class in many cases to set up the endpoint authorization rules and the authentication method in Spring apps. If this class doesn’t sound familiar to you, I invite you to learn everything you need on Spring Security fundamentals from my YouTube channel where I posted a complete set of tutorials on this subject. Alternatively, you could of course learn Spring Security from Spring Security in Action, a book I wrote.
This is the Spring Security fundamentals playlist on my YouTube channel:
This is Spring Security in Action:
In this article, we discuss a different fashion of implementing the endpoint authorization with Spring Security in a Spring app. Instead of extending the WebSecurityConfigurerAdapter class and overriding the configure() method, you define a simple bean of type SecurityFilterChain.
You find the associated source code for this article here: https://github.com/lspil/blog/tree/master/endpoint-authorization-methods
So, let’s start by remembering how to set the endpoint security configurations using the WebSecurityConfigurerAdapter class. This is a method you will often encounter in many apps. You probably used it various times as well.
Let’s take a look at the following code snippet. It defines a UserDetailsService and a PasswordEncoder and configures that all the requests need authentication. Of course, to make the example simpler, I use an InMemoryUserDetailsManager in which I add two test users. I also use the NoOpPasswordEncoder. You should never use this PasswordEncoder implementation in a real-world example because it doesn’t encrypt the passwords anyhow.
You see how the class extends WebSecurityConfigurerAdapter and overrides the configure(HttpSecurity http) method. In the configure(HttpSecurity http) method, we specify the authentication method as HTTP Basic and the authorization rules.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Configuration public class ProjectConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { var uds = new InMemoryUserDetailsManager(); uds.createUser(User.withUsername("bill").password("12345").authorities("read").build()); uds.createUser(User.withUsername("john").password("12345").authorities("write").build()); return uds; } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic(); // configuring HTTP Basic for authentication http.authorizeRequests() // specifying that any request needs authentication .anyRequest().authenticated(); } } |
Good enough! But why is this configuration approach not always comfortable? Well, you know…Java… class inheritance is not multiple. Configuration based on class inheritance isn’t usually a desired class design because you could get into situations where, to configure different things, you need to extern multiple classes…which…of course…is not a Java thing.
A better approach is to rely on object instances. What about using a bean for the configuration? In the end, we define a bean for the user management, and we define one for password management as well. Why couldn’t we define a bean for the endpoint security configuration as well? Well, we can. Take a look at the following code snippet. We change the configuration class, to define the endpoint security configuration using a bean of type SecurityFilterChain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @Configuration public class ProjectConfig { @Bean public UserDetailsService userDetailsService() { var uds = new InMemoryUserDetailsManager(); uds.createUser(User.withUsername("bill").password("12345").authorities("read").build()); uds.createUser(User.withUsername("john").password("12345").authorities("write").build()); return uds; } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean public SecurityFilterChain configuration(HttpSecurity httpSecurity) throws Exception { return httpSecurity .httpBasic() // configuring HTTP Basic for authentication .and() .authorizeRequests() .anyRequest().authenticated() // specifying that any request needs authentication .and() .build(); } } |
The result is similar to the first snippet where we extended WebSecurityConfigurerAdapter. But from a class design perspective, it’s less restrictive because you don’t need to extend any class for overriding a specific method. You can use SecurityFilterChain to implement the same configurations you could do with the other approach. In the next snippet, you see an example where we restrict the requests to a certain authority.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Configuration public class ProjectConfig { @Bean public UserDetailsService userDetailsService() { var uds = new InMemoryUserDetailsManager(); uds.createUser(User.withUsername("bill").password("12345").authorities("read").build()); uds.createUser(User.withUsername("john").password("12345").authorities("write").build()); return uds; } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean public SecurityFilterChain configuration(HttpSecurity httpSecurity) throws Exception { return httpSecurity .httpBasic() .and() .authorizeRequests().anyRequest().hasAuthority("read") // only users with the authority "read" // can send requests. .and() .build(); } } |
You find the associated source code for this article here: https://github.com/lspil/blog/tree/master/endpoint-authorization-methods
To test the configuration, create a demo controller class and use a tool such as Postman or cURL to call the endpoint. Try to use different security configurations to prove that the two approaches work similarly.
2 Comments
Is there any where to configure multiple SecurityFilterChain bean and is expected to be inheritance? I’m building an application with micro service architecture and is expect to have a common filter chain (eg. cors, csrf, actuator, etc) and compile it with each of the micro services by defining another layer of filter chains.
No inheritance is expected (as presented in the article). I didn’t try something like building a separate reusable configuration but I don’t see why that’d not be possible.
The only thing is that the configuration is usually small enough and direct to a specific service. So your approach might be overengineering.