How Basic Authentication works in Spring Security?
I have already described the Spring Security Authentication Architecture in a previous article. So i am not going to repeat the same thing again in this article. If you do not know about the general authentication architecture of spring security, it is highly recommend to take a look at article about Spring Security Authentication Architecture before continue with this article.
Here it is expected to point out the major components and classes that are related to HTTP Basic authentication. Here is the architectural flow of HTTP Basic Authentication implementation in spring security.
How to configure Spring Security for HTTP Basic Authentication?
When you use the httpBasic() configuration element (In HttpSecurity configuration), Spring Security BasicAuthenticationFilter comes into action.
In Spring Security, the following two classes are the main core (important) classes that supports to implement HTTP Basic Authentication.
- BasicAuthenticationFilter
- BasicAuthenticationEntryPoint
A BasicAuthenticationEntryPoint strategy will be configured into the ExceptionTranslationFilter on startup.
Here is the Spring Security Basic Authentication Architecture diagram.
Here you will notice that, if the authentication mechanism is HTTP Basic, then the related AuthenticationFilter class will be the BasicAuthenticationFilter. It accepts the HTTP request (for user authentication) and extracts the username and password (from http request).
Then it creates the Authentication Token (UsernamePasswordAuthenticationToken) using the extracted user credentials. After that it delegates the Authentication request to the AuthenticationManager and waits until the authentication response comes from the authentication manager.
Response from AuthenticationManager
If you just check the source code of the AuthenticationManager interface, you may notice that it can produce two types of responses. Therefore BasicAuthenticationFilter can expect these two types of responses from the authentication manager.
- returns fully populated Authenticated Object (On successful Authentication)
- throws an AuthenticationException (On Authentication Failure)
If the authentication is successful, a fully populated authentication object will be returned and it will be stored in the SecurityContext.
If any authentication failure happens, it will throw an AuthenticationException. This AuthenticationException will be initially received and handled by the ExceptionTranslationFilter and it will be delegated to the BasicAuthenticationEntryPoint for further consideration/action.
What is BasicAuthenticationEntryPoint?
If any AuthenticationException is occurred in spring security filter chain, it will be delegated to the the related/configured AuthenticationEntryPoint for further action.
If the Authentication mechanism is HTTP Basic, then the relevant AuthenticationEntryPoint will be BasicAuthenticationEntryPoint. This will accepts the AuthenticationException occurred during the authentication process (HTTP Basic) and will help to generate user response with meaningful headers to notify the client about the authentication failure.
Ok. Lets look at a sample spring security project that uses http basic authentication for securing its REST Api endpoints.
The fully source code of this example can be found at GitHub – Click here to go to project repository
Here is the project structure.
SpringSecurityConfig.java
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; | |
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.WebSecurityConfigurerAdapter; | |
import org.springframework.security.config.http.SessionCreationPolicy; | |
import org.springframework.security.crypto.password.NoOpPasswordEncoder; | |
@Configuration | |
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) | |
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { | |
@Override | |
protected void configure(AuthenticationManagerBuilder auth) throws Exception { | |
auth.inMemoryAuthentication().withUser("app_user").password("test123").roles("USER"); | |
auth.inMemoryAuthentication().withUser("admin").password("test123").roles("USER", "ADMIN"); | |
auth.inMemoryAuthentication().withUser("chathuranga").password("test123").roles("USER", "ADMIN"); | |
} | |
@Override | |
protected void configure(HttpSecurity http) throws Exception { | |
http.httpBasic(). | |
realmName("spring-app"). | |
and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS). | |
and().csrf().disable(). | |
authorizeRequests().antMatchers("/guest/**").permitAll().anyRequest().authenticated(); | |
} | |
@Bean | |
public NoOpPasswordEncoder passwordEncoder() { | |
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); | |
} | |
} |
According to the spring configuration, it uses HTTP Basic Authentication (http.httpBasic()) and any request for any resource should be authenticated. In addition, It creates three users in memory with following roles and credentials.
Username | Password | Roles |
app_user | test123 | ROLE_USER |
admin | test123 | ROLE_USER, ROLE_ADMIN |
chathuranga | test123 | ROLE_USER, ROLE_ADMIN |
PasswordEncoder in Spring Security 5
@Bean public NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); }
When we use Spring Security 5, we need to explicitly provide the PasswordEncoder that our passwords are encoded with. It was not a requirement with Spring Security 4.x versions. But this has been mandatory in Spring Security 5 regardless of the AuthenticationProvider. Therefore this guaranteed that your passwords are stored in encrypted format and clear text password are no longer supported.
But when it comes to creating in-memory authentication provider for testing and development purposes, you might need to declare clear text password in the source code as i have done in this example. Since the Spring Security 5 forces to explicitly declare type of PasswordEncoder regardless of the authentication provider., we can use NoOpPasswordEncoder as the PasswordEncoder.
NoOpPasswordEncoder is a password encoder that does nothing. Useful for testing where working with plain text passwords.
So just look at the RestApiController. You will see a list of supported REST Api endpoints for each user role.
import org.springframework.security.access.annotation.Secured; | |
import org.springframework.security.access.prepost.PostAuthorize; | |
import org.springframework.security.access.prepost.PreAuthorize; | |
import org.springframework.security.core.annotation.AuthenticationPrincipal; | |
import org.springframework.security.core.userdetails.User; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
import javax.annotation.security.RolesAllowed; | |
@RestController | |
public class RestApiController { | |
@Secured("ROLE_USER") | |
@GetMapping(value = "/user/hello") | |
public String welcomeAppUser(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@Secured({"ROLE_USER", "ROLE_ADMIN"}) | |
@GetMapping(value = "/user/hello4") | |
public String welcomeAppUser4(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@PreAuthorize("hasRole('USER')") | |
@GetMapping(value = "/user/hello1") | |
public String welcomeAppUser1(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@PreAuthorize("hasRole('USER') AND hasRole('ADMIN')") | |
@GetMapping(value = "/user/hello2") | |
public String welcomeAppUser2(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@PreAuthorize("hasRole('USER') OR hasRole('ADMIN')") | |
@GetMapping(value = "/user/hello3") | |
public String welcomeAppUser3(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
/** | |
* <p> | |
* This method can be accessed by any user with ROLE_USER. | |
* But the content will be returned if the user has the ROLE_ADMIN and | |
* authenticated principal name is same as the username of the return object. | |
* </p> | |
* | |
* @param user | |
* @return | |
*/ | |
@PreAuthorize("hasRole('USER')") | |
@PostAuthorize("(returnObject.username == principal.name) AND hasRole('ADMIN')") | |
@GetMapping(value = "/user/hello5") | |
public UserProfile welcomeAppUser5(@AuthenticationPrincipal User user) { | |
return new UserProfile("chathuranga", "Chathuranga Tennakoon"); | |
} | |
@RolesAllowed("ROLE_USER") | |
@GetMapping(value = "/user/hello6") | |
public String welcomeAppUser6(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@RolesAllowed({"ROLE_USER", "ROLE_ADMIN"}) | |
@GetMapping(value = "/user/hello7") | |
public String welcomeAppUser7(@AuthenticationPrincipal User user) { | |
return "Welcome User " + user.getName(); | |
} | |
@GetMapping(value = "/guest/user",produces = "application/xml") | |
public UserProfile guestUser() { | |
return new UserProfile("chathuranga","123"); | |
} | |
} |
How to deploy the application?
You can get the fully source code of the application through the following Github location.
https://github.com/chathurangat/spring-security-http-basic-authentication
download the source files and cd into the project directory.
cd spring-security-http-basic-authentication
After that you can tun following command to deploy and run the application.
mvn spring-boot:run
Now you can access any of above endpoints with valid user credentials (as listed in above table) with HTTP Basic Authentication. Sample postman request can be shown as follows.
Authorization Basic base64encode(username:password)
Hope that after reading this article, you have improved your understanding of how HTTP Basic authentication is used in spring security to secure REST api endpoints.
Wow, thank you a lot!
Simple and direct to the point!
LikeLike