When we create an API for providing services or an web application, it is crucial to provide authentication and authorization to the project. In this post I am going to tell how to use Spring Security module to make the necessary security configurations.
To start, I am going to create a RestController with two endpoints "/", "/welcome" and try to run the application and see what happens.
@RestController
public class HomeController {
@GetMapping("/")
public String sayHello() {
return "Welcome to Spring Security!";
}
@GetMapping("/welcome")
public String sayHello() {
return "Welcome to Spring Security(Protected)!";
}
}
Here is the result !
Lets see what happens if we add spring security to the project dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Now if we run the application and try to hit and endpoint you will be redirected to a login page which will look like below.
Well, I don't remember adding a /login
endpoint nor configured any users. But how the login page rendered and why?
Spring security is enabled just by adding the dependency to the classpath. By default all the routes/endpoints needs authentication to access. But what credentials are used to login? I don't remember configuring any credentials!. It is because if we didn't configure it spring will provide a default random credentials at run time
Security Configuration
Spring MVC sends all incoming HTTP requests into a single servlet named DispatcherServlet
after passing through set of filters. The DispatcherServlet is responsible to send the HTTP request to the corresponding Controller.
When we add Spring Security to the classpath a set of Security Filters are added to the FilterChain. These filters intercept every HTTP request before they come to the Controllers. This is where Spring security authenticates the HTTP requests and decides either to forward or reject the request.
By default, every HTTP request is authenticated. But, we can override this behaviour by making our own security configuration
In order to do that, we need to provide our custom SecurityFilterChain
bean
Default behaviour is due to the bean
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().anyRequests().authenticated();
http.formLogin();
http.httpBasic();
return http.build();
}
Now, we can have our own bean in a brand new configuration class like this.
@Configuration
class ApiSecurityConfiguration {
@Bean
SecurityFilterChain customSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests)-> requests
.requestMatchers("/welcome").authenticated()
.anyRequests().authenticated());
.formLogin();
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
The above configuration makes /welcome
endpoint is authenticated and any endpoint other than that are publicly accessible
But we still have not set up our own set of users.
Before moving forward with Users setup, we need to understand the how the request is processed.
UserDetailsService
(Interface) -> loadUserByUserName(String username)
This is the Core Interface which loads user specific data
UserDetailsManager
(Interface extends UserDetailsService) Extension of userDetailsService which allows to create, update, delete UserDetails (see method signatures below)
createUser(UserDetails user)
updateUser(UserDetails user)
deleteUser(String username user)
changePassword(String oldPwd, String newPwd)
userExists(String username)
Sample Implementation classes ( for UserDetailsManager
interface ) provided by Spring Security are InMemoryUserDetailsManager
, JdbcUserDetailsManager
, LdapUserDetailsManager
All the above interfaces and classes use an interface
UserDetails
which provides core user information
For proving user details for authetication, we need to implement UserDetailsService
interface and provide it as a spring bean. We can either use the default implementations provided by spring or we can have our own custom implementation.
If we are building enterprise application, it is recommended to have your own custom implementation. But for simplicity, we will use the default implementations provided
In this post, we will see how we can configure InMemoryUserDetailsManager
@Bean
UserDetailsService userDetailsService() {
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("12345")
.authorities("admin")
.build();
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("54321")
.authorities("read")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
Here, we are using DefaultPasswordEncoder()
. But, in general we need to provide a PasswordEncoder
bean. Spring provides us with many password encoders
For Example,
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptyPasswordEncoder()
}
Now, with these configurations, we can access the protected routes using the provided users in InMemory.
We can also get users from database using default JdbcUserDetailsManager
bean offered by spring Security
In order to use this we need to have a datasource
We are going to use mysql for this application. Datasource configurations are done in applications.properties
file or we have application.yml
spring:
datasource:
platform: mysql
url: jdbc:mysql://localhost:5432/mydb
username: foo
password: bar
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
By Default JdbcUserDetailsManager
default implementation, assumes we follow a database schema which has users(id, username, password, enabled)
and authorities(id, username, authority)
tables
So, if we are using JdbcUserDetailsManager
implementation we need to follow the same ddl schema
Replace InMemoryUserDetailsManager
bean with JdbcUserDetailsManager
bean (Make sure you pass DataSoucrce object)
@Bean
UserDetailsService userDetailsService(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource)
}
Well, we have configured our application to use InMemory UserDetails and configured to fetch user details from database as well using default implementations.
But this is not enough for enterprise application, since we might not follow the same naming convention in the database based on client recommendation. In that case we cannot use JdbcUserDetailsManager
.
But that is a topic for another day.
I will write more about it in my next post.
Top comments (0)