Spring Security -- 3) UserDetailsManager, JdbcUserDetailsManager and BCryptPasswordEncoder
In this post, let’s look at the what UserDetailsManager is, differences between UserDetailsService. And also I am going to implement a project …
In this post, I am going to answer to this question “what is the Authentication Provider” and I am going to implement a project includes custom authentication provider.
If you only need to see the code, here is the github link
You might need an architecture which authentication process can not be done via username and password. In other words you might need to implement your own authentication logic. In that case, you can implement custom authentication provider.
In default I have a dummy user creating via InMemoryUserDetailsManager
You only need these dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Controller:
package com.mehmetozanguven.springsecurityauthenticationprovider.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
Configuration:
package com.mehmetozanguven.springsecurityauthenticationprovider.config;
@Configuration
public class ProjectBeanConfiguration {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
UserDetails dummyUser = User.withUsername("dummy_user")
.password("1234")
.authorities("read")
.build();
inMemoryUserDetailsManager.createUser(dummyUser);
return inMemoryUserDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
Instead of default AuthenticationProvider
provided by Spring, let’s use a custom one.
In general AuthenticationProvider
contains two methods: authenticate()
contains the authentication logic and supports()
contains the logic which this authentication provider should be applied or not.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
For the authenticate()
method, there are three options:
For the supports()
method:
return UsernamePasswordAuthenticationToken.class.equals(authentication);
means that authentication should be processed when authentication type is UsernamePasswordAuthenticationToken (httpBasic uses this authentication)Here is the MyCustomAuthenticationProvider
:
Note: When you are returning fully authenticated instance, return an instance that implements Authentication interface and
Authenticate#isAuthenticated
must return true.For httpBasicAuthentication, instance will be
UsernamePasswordAuthenticationToken,
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { // for fully authentication instance, DO NOT USE THIS ONE !!! public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); } // for fully authentication instance, USE THIS ONE public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); } }
/**
* This class includes the following logic:
* If a user exists and password is correct, then login must be successful
* otherwise login should fail
*/
@Component
public class MyCustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// In real case scenarios, userDetailsService should throw an error when user is not found
// therefore there is no need for null check
if (userDetails != null){
if (passwordEncoder.matches(password, userDetails.getPassword())){
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
return authenticationToken;
}
}
throw new BadCredentialsException("Error!!");
}
/**
* Because I am going to use HttpBasicAuthentication
* and HttpBasicAuthentication uses UsernamePasswordAuthenticationToken
* @param authenticationType
* @return
*/
@Override
public boolean supports(Class<?> authenticationType) {
return UsernamePasswordAuthenticationToken.class.equals(authenticationType);
}
}
The last step is to add the custom provider to the configuration:
@Configuration
public class ProjectBeanConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MyCustomAuthenticationProvider myCustomAuthenticationProvider;
// ...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myCustomAuthenticationProvider);
}
}
After starting the application, run the following curl command: (You can also add the debug point to the custom authentication provider)
[mehmetozanguven@localhost ~]$ curl --user dummy_user:1234 -X GET http://localhost:8080/hello
hello
I will continue with the next one …
In this post, let’s look at the what UserDetailsManager is, differences between UserDetailsService. And also I am going to implement a project …
In this post, I am going to use real database to check the user against the request(s). Github Link If you only need to see the code, here is the …