Spring In-Memory authentication uses InMemoryUserDetailsManager internally store and retrieve the user-related information which is required for Authentication. In this InMemoryUserDetailsManager Example, we will learn how to create and store the user to the in-memory HashMap, update the user credentials and finally delete the User.
So whenever the user requests for any details, the request is filtered and passed to AuthenticationManager, which tries to authenticate the request by the using UserDetailsService.
The UserDetailsService is responsible for retrieving the correct user details, InMemoryUserDetailsManager indirectly implements UserDetailsService interface. Now the InMemoryUserDetailsManager reads the in-memory hashmap and loads the UserDetails by calling the loadUserByUsername() method.
Once the UserDetails is loaded via InMemoryUserDetailsManager and the authentication is successful, the SecurityContext will be updated and the request will proceed to the DispatcherServlet and so on..
Folder Structure:
- Create a simple Maven Project “SpringInMemoryExample” and create a package for our source files “com.javainterviewpoint.config” and “com.javainterviewpoint.controller” under src/main/java
- Now add the following dependency in the POM.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javainterviewpoint</groupId> <artifactId>SpringInMemoryExample</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringInMemoryExample</name> <build> <finalName>SpringInMemoryExample</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.3.6.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> </project>
- Create the Java class HelloController.java, ServletInitializer.java, SpringSecurityConfig.java and SecurityInitializer.java under com.javainterviewpoint folder.
Spring Security – InMemoryUserDetailsManager Example
Spring Security Configuration – InMemoryUserDetailsManager
package com.javainterviewpoint.config; import java.util.ArrayList; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Configuration @EnableWebMvc @EnableWebSecurity @ComponentScan(basePackages = { "com.javainterviewpoint" }) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(inMemoryUserDetailsManager()); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/employee**").hasRole("USER") .antMatchers("/manager**").hasRole("MANAGER") .anyRequest().authenticated() .and() .httpBasic() .and() .csrf().disable(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public InMemoryUserDetailsManager inMemoryUserDetailsManager() { List<UserDetails> userDetailsList = new ArrayList<>(); userDetailsList.add(User.withUsername("employee").password(passwordEncoder().encode("password")) .roles("EMPLOYEE", "USER").build()); userDetailsList.add(User.withUsername("manager").password(passwordEncoder().encode("password")) .roles("MANAGER", "USER").build()); return new InMemoryUserDetailsManager(userDetailsList); } }
- @EnableWebMvc is equivalent to <mvc:annotation-driven />. It enables support for @Controller, @RestController, etc.. annotated classes
- @EnableWebSecurity annotation enables spring security configuration which is defined in WebSecurityConfigurerAdapter
- We have extended WebSecurityConfigurerAdapter, which allows us to override spring’s security default feature. In our example we want all the requests to be authenticated using the custom authentication.
- configure(HttpSecurity http) method configures the HttpSecurity class which authorizes each HTTP request which has been made. In our example ‘/employee**’ should be allowed for the user with USER role and ‘/manager**’ should be allowed for the user with MANAGER role.
- authorizeRequests() .antMatchers(“/employee**”).hasRole(“USER”) .antMatchers(“/manager**”).hasRole(“MANAGER”) –> All requests to must be authorized or else they should be rejected.
- httpBasic() –> enables the Basic Authentication
- .csrf().disable() –> Disables CSRF protection
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/employee**").hasRole("USER") .antMatchers("/manager**").hasRole("MANAGER") .anyRequest().authenticated() .and() .httpBasic() .and() .csrf().disable(); }
- configure(AuthenticationManagerBuilder auth) method configures the AuthenticationManagerBuilder class with the valid credentials and the allowed roles. The AuthenticationManagerBuilder class creates the AuthenticationManger which is responsible for authenticating the credentials. In our example, we have used the InMemoryUserDetailsManager as the UserDetailsService
- inMemoryUserDetailsManager() methods create all the in-memory UserDetails, to start with we have added two users employee and manager.
@Bean public InMemoryUserDetailsManager inMemoryUserDetailsManager() { List<UserDetails> userDetailsList = new ArrayList<>(); userDetailsList.add(User.withUsername("employee").password(passwordEncoder().encode("password")) .roles("EMPLOYEE", "USER").build()); userDetailsList.add(User.withUsername("manager").password(passwordEncoder().encode("password")) .roles("MANAGER", "USER").build()); return new InMemoryUserDetailsManager(userDetailsList); }
Registering Spring Security Filter
Spring Security will be implemented using DelegatingFilterProxy, in order to register it with the Spring container we will be extending AbstractSecurityWebApplicationInitializer. This will enable Spring to register DelegatingFilterProxy and use the springSecurityFilterChain Filter
package com.javainterviewpoint.config; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer { }
ServletInitializer
From Servlet 3.0 onwards, ServletContext can be programmatically configured and hence web.xml is not required.
We have extended AbstractAnnotationConfigDispatcherServletInitializer class which in turn implements WebApplicationInitializer, the WebApplicationInitializer configures the ServletContext
package com.javainterviewpoint.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] {SpringSecurityConfig.class}; } @Override protected String[] getServletMappings() { return new String[] {"/"}; } }
HelloController
package com.javainterviewpoint.controller; import java.util.ArrayList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired public InMemoryUserDetailsManager inMemoryUserDetailsManager; @Autowired public PasswordEncoder passwordEncoder; @GetMapping("/employee") public String welcomeEmployee() { return "Welcome Employee"; } @GetMapping("/manager") public String welcomeManager() { return "Welcome Manager"; } @GetMapping("/employee/{username}") public String checkIfUserExists(@PathVariable("username") String username) { boolean flag = inMemoryUserDetailsManager.userExists(username); if (flag) return "\""+username + "\" exist in InMemoryUserDetailsManager"; else return "\""+username + "\" does not exist in InMemoryUserDetailsManager"; } @GetMapping("/employee/create/{username}/{password}/{role}") public String createUser(@PathVariable("username") String username, @PathVariable("password") String password, @PathVariable("role") String role) { ArrayList<GrantedAuthority> grantedAuthoritiesList= new ArrayList<>(); grantedAuthoritiesList.add(new SimpleGrantedAuthority(role)); inMemoryUserDetailsManager.createUser(new User(username, passwordEncoder.encode(password), grantedAuthoritiesList)); return checkIfUserExists(username); } @GetMapping("/employee/update/{username}/{password}/{role}") public String updateUser(@PathVariable("username") String username, @PathVariable("password") String password, @PathVariable("role") String role) { ArrayList<GrantedAuthority> grantedAuthoritiesList= new ArrayList<>(); grantedAuthoritiesList.add(new SimpleGrantedAuthority(role)); inMemoryUserDetailsManager.updateUser(new User(username, passwordEncoder.encode(password), grantedAuthoritiesList)); return checkIfUserExists(username); } @GetMapping("/employee/delete/{username}") public String deleteUser(@PathVariable("username") String username) { inMemoryUserDetailsManager.deleteUser(username); return checkIfUserExists(username); } }
We have autowired InMemoryUserDetailsManager and PasswordEncoder classes, InMemoryUserDetailsManager enables us to create, retrieve, modify and delete the UserDetails and PasswordEncoder is an implementation of BCryptPasswordEncoder used to encode the password.
All the request which start with /employee requires USER role and the request which start with /manager requires MANAGER role. checkIfUserExists(), createUser(), updateUser(), deleteUser() methods will help us to make changes to the in-memory UserDetails
Output:
Check if the User Exist or Not
In POSTMAN, select GET method and hit the URL “http://localhost:8080/SpringInMemoryExample/employee/employee”
In the Authorization, tab select the Type as “Basic Auth” and key in the valid username /password [employee/password (or) manager/password]. You should get a response like“employee” exist in InMemoryUserDetailsManager
Now hit “http://localhost:8080/SpringInMemoryExample/employee/employee111” and the response will be “employee111” does not exist in InMemoryUserDetailsManager
Create User
GET request on the URL “http://localhost:8080/SpringInMemoryExample/employee/create/john/pass/ROLE_USER”
In the Authorization, tab select the Type as “Basic Auth” and key in the valid credentials (employee/password)
This adds the User john to the In-Memory UserDetails
Update User
Now let’s update the password for the user john.
Place a GET request on the URL “http://localhost:8080/SpringInMemoryExample/employee/update/john/password/ROLE_USER”
In the Authorization, tab select the Type as “Basic Auth” and key in the valid credentials (employee/password)
Let’s now try to login with the user john and validate whether we are able to access the /employee service as it needs the USER role
Hit on the URL “http://localhost:8080/SpringInMemoryExample/employee” with credentials (john/password)
Try accessing the /manager service, it should be 403 Forbidden as it requires MANAGER role
Delete User
Let’s delete the user john
Hit on the URL “http://localhost:8080/SpringInMemoryExample/employee/delete/john”
Leave a Reply