Deep Dive into Spring Security Authentication Implementation on WebFlux, Part I: Basic Authentication
This article is part one of two-part articles on Spring Security Authentication Implementation on WebFlux.
During the time I created my personal project with microservices architecture, I want to implement basic and JWT authentication on my Spring WebFlux project using Spring Security. Just like any other day, I just googled it. But what stumbled me at that time, is that there are only a few resources I found that give the example of how to implement it and they are all giving different solutions.
After reading all examples available online, I become more confused, because there is no explanation on the code structure. So, in this article, I want to try to give more detailed explanation on how Spring Security authentication works.
CREDIT: I want to thank everyone behind all these articles to provide free high-quality Spring Security learning materials:
1. Reactive Spring Security For WebFlux REST Web Services — A Complete (naturalprogrammer.com)
2. Spring Security 5 for Reactive Applications | Baeldung
3. raphaelDL/spring-webflux-security-jwt: A JWT authorization and authentication implementation with Spring Reactive Webflux, Spring Boot 2 and Spring Security 5 (github.com)
4. Authentication and Authorization Using JWT on Spring Webflux | by Ardiansyah | Medium
DISCLAIMER: This article is written by my limited knowledge of Spring Security, there is a chance that this article will give incomplete or wrong information.
Chain of Filters
When we add spring-boot-starter-security dependency to our Spring Boot project, the package will automatically create chain of WebFilter
that every request needs to go through. We can configure the chain by creating bean of SecurityWebFilterChain
.
At the example below, we allow all paths to be accessed by everyone as stated on line 12–13. We also add .formLogin()
to our configuration.
What formLogin()
does is basically adding AuthenticationWebFilter
, one of implementation of WebFilter
, to the chain of filters that process request to path/login
for POST
and GET
method.
Diving on AuthenticationWebFilter
When the we send HTTP POST request to /login
that has basic authentication on its header, the request will be processed by AuthenticationWebFilter
. There are many processes happen behind it as shown in the image below and I will explain it one by one.
On the diagram above, Authentication
instance passed through class stack many times. But what is Authentication
instance is? It’s basically an object to save principal, credential, authority and authentication status. In this case, we will save username on and password on Authentication
object’s principal
and credentials
field respectively. There is also authenticated
field on this interface to save state whether the given credential is valid. Click here for more detailed information on Authentication
interface.
In basic authentication flow case Spring Security use UsernamePasswordAuthenticationToken
, implementation of Authentication
interface. Click here for more information about this class.
1 — ServerAuthenticationConverter
ServerAuthenticationConverter
is a FunctionalInterface
that is responsible to convert data on HTTP request to Authentication
object. In basic authentication flow, Spring Security use ServerHttpBasicAuthenticationConverter
as implementation of ServerAuthenticationConverter
.
ServerHttpBasicAuthenticationConverter
will return UsernamePasswordAuthenticationToken
instance with authenticated
field set as false
at first and pass it back to AuthenticaitonWebFilter
. Later the username and password will be checked by ReactiveAuthenticationManager
. If matched, authenticated
field will be set to true
.
ForServerHttpBasicAuthenticationConverter
source code click here.
2 — ReactiveUserDetailsService
ReactiveUserDetailsService
is an interface that has only one method to find UserDetails
by given Username. The implementation of ReactiveUserDetailsService
usually take the valid credentials from database and return implementation of UserDetails
.
So, what is UserDetails
? it’s an interface that its implementation has valid credentials on the certain username. The commonly used implementation of this interface is User
class. User
object has valid credentials that will be compared to requested credentials on Authentication
object.
But for the sake of simplicity, I won’t explain on how to implement ReactiveUserDetailsService
that use R2DBC to get data from database asynchronously. Instead, I will use built-in in-memory implementation of this class using MapReactiveUserDetailsService
like the code below.
ReactiveUserDetailsService
has one method, loadUserByUsername
, that will be used by ReactiveAuthenticationManager
to get UserDetails
object.
PasswordEncoder
on this class will be injected from declared bean from next section.
3 — PasswordEncoder
On password encoder, I use built-in PasswordEncoder
called DelegatingPasswordEncoder
. This type of PasswordEncoder
can handle different types of hash algorithm. Luckily, Spring Security gives us built-in builder for this class as shown below.
The built DelegatingPasswordEncoder
object has bcrypt hash algorithm as default encoder. But it also be able to handle password that already hashed with md5, sha-1, etc. Click here to read more details on DelegatingPasswordEncoder
.
This PasswordEncoder
will be used to match between given password on authentication request and hashed password in UserDetailsService
object.
4 — ReactiveAuthenticationManager
ReactiveAuthenticationManager
is an interface that will be implemented on a class that’s responsible to decide whether the authentication request is valid. By default, when we declare .formLogin() to modify SecurityWebFilterChain
it will use UserDetailsRepositoryReactiveAuthenticationManager
class, one of ReactiveAuthenticationManager
inheritance, by default.
This class will take unauthenticated Authentication
object from AuthenticationConverter
, and matched the given password and hashed password using PasswordEncoder
, in this case DelegatingPasswordEncoder
. The byproduct of this process is authenticated Authentication
object if the authenticate method success, otherwise it will return error.
The shown code above is taken from source code of built-in Spring Security AbstractUserDetailsReactiveAuthenticationManager
. As you can see on line 9 of the code snippet above, it uses the injected PasswordEncoder
to match the password between authentication request and hashed password on UserDetails.
5 — AuthenticationHandler
After ReactiveAuthenticationManager
stack finished, the AuthenticationWebFilter
will call AuthenticationHandler
. In this case, we have two handlers to handle successful and fail authentication attempt. They are WebFilterChainServerAuthenticationSuccessHandler
class that implement ServerAuthenticationSuccessHandler
and class that implement ServerAuthenticationFailureHandler
respectively.
We inject those two classes on SecurityWebFilterChain
configuration as shown in the code snippet on Chain of Filters section above.
what WebFilterChainServerAuthenticationSuccessHandler
do are passing the request to the next filter as shown in code below taken from Spring Security source code.
For the ServerAuthenticationFailureHandler
I just pass the implementation by using lambda expression that will respond error if it’s called.
Controller
If the request passed SecurityWebFilterChain
successfully, the WebFilterChainServerAuthenticationSuccessHandler
pass the request to POST /login
controller. We can get the Authentication
object from publisher’s context by adding Authentication
parameter to controller.
On the code above, I give response contains headers with JWT Token and body with simple string. I give two option to get username on controller one from Authentication
object, the other from request. I prefer to get the data from Authentication
object because I think it’s more secure, but I just want to show you all the options.
Test
I sent the POST request to the server with username and password on the form as seen below.
As you can see on the images above, the response of the server is as expected. But, in cookies section, we still get SESSION cookie and the JWT hasn’t been set correctly. these problems will be explained on part two of this article.
Conclusion
This is my first time write an article about Spring, I’m really open to any suggestions and feedbacks. I hope this article will help anyone trying to implement Spring Security on Spring WebFlux.
Full source code is available on my GitHub.