I'm learning about Spring Security in a Spring Boot app and I have a very simple example. And I see that if I comment the configure(AuthenticationManagerBuilder auth)
there is no difference. If I use it or not I have the same output, and I need to login with the hardcoded credentials.
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// private final MyUserDetailsService myUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(myUserDetailsService);
// }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
MyUserDetailsService class:
@Service
public class MyUserDetailsService implements UserDetailsService {
private static final String USERNAME = "john";
private static final String PASSWORD = "$2a$10$fDDUFA8rHAraWnHAERMAv.4ReqKIi7mz8wrl7.Fpjcl1uEb6sIHGu";
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if (!userName.equals(USERNAME)) {
throw new UsernameNotFoundException(userName);
}
return new User(USERNAME, PASSWORD, new ArrayList<>());
}
}
RestController:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}
}
I want to know if implementing the UserDetailsService
interface is equivalent with overriding the configure(AuthenticationManagerBuilder auth)
. Thank you!
The Spring documentation suggests: Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users. If you are only creating a service that is used by non-browser clients, you will likely want to disable CSRF protection.
First of all, according to Spring Boot dot, we have to add @EnableWebSecurity annotation. Second of all, we have to override configure method WITH @Override annotation AND super. configure(http) at the end of the method.
The default strategy is if configure(AuthenticationManagerBuilder) method is overridden to use the AuthenticationManagerBuilder that was passed in.
In Spring Boot 2, if we want our own security configuration, we can simply add a custom WebSecurityConfigurerAdapter. This will disable the default auto-configuration and enable our custom security configuration. Spring Boot 2 also uses most of Spring Security's defaults.
UserDetailsService is used by DaoAuthenticationProvider for retrieving a username, password, and other attributes for authenticating with a username and password. Spring Security provides in-memory and JDBC implementations of UserDetailsService.
You can define custom authentication by exposing a custom UserDetailsService as a bean. For example, the following will customize authentication assuming that CustomUserDetailsService implements UserDetailsService
The UserDetailsService interface is used to retrieve user-related data. It has one method named loadUserByUsername()
which can be overridden to customize the process of finding the user. In order to provide our own user service, we will need to implement the UserDetailsService interface.
loadUserByUsername(String username)
returns the UserDetails which is part of org.springframework.security.core.userdetails
which consists of getUsername(), getPassword(), getAuthorities()
methods which is used further for spring security.
We can also customize the org.springframework.security.core.userdetails.User
(here used as new User(USERNAME, PASSWORD, new ArrayList<>())
) by implemeting the UserDetails interface.
Here, I am sharing the ideal way to use the UserDetailsService service
@Component("userDetailsService")
public class DomainUserDetailsService implements UserDetailsService {
private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);
private final UserRepository userRepository;
public DomainUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public UserDetails loadUserByUsername(final String login) {
log.debug("Authenticating {}", login);
if (new EmailValidator().isValid(login, null)) {
return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login)
.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
}
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
.map(user -> createSpringSecurityUser(lowercaseLogin, user))
.orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
}
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
if (!user.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getLogin(),
user.getPassword(),
grantedAuthorities);
}
}
As described above, It is typically called by DaoAuthenticationProvide instance in order to authenticate a user. For example, when a username and password is submitted, a UserdetailsService is called to find the password for that user to see if it is correct. It will also typically provide some other information about the user, such as the authorities and any custom fields you may want to access for a logged in user (email, for instance)
Here you have used the static values for username and password which can be ideally configured using the In-Memory Authentication as follow.
Spring Security’s InMemoryUserDetailsManager
implements UserDetailsService
to provide support for username/password based authentication that is retrieved in memory. InMemoryUserDetailsManager
provides management of UserDetails
by implementing the UserDetailsManager
interface. UserDetails
based authentication is used by Spring Security when it is configured to accept a username/password for authentication.
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
This method uses AuthenticationManagerBuilder
which internally use SecurityBuilder to create an AuthenticationManager. Allows for easily building in memory authentication, LDAP authentication, JDBC based authentication, adding UserDetailsService, and adding
AuthenticationProvider.
How Spring Security add/configure AuthenticationManagerBuilder?
UserDetailsService interface is equivalent with overriding the configure(AuthenticationManagerBuilder auth)
No
No, it's not same.
User details service provided in the application as bean is registered with global authentication manager (details
) and is fallback for all the local authentication manager.
Depending on application set up can have multiple local authentication managers. Each local authentication manager will use the default user details service configured with configure(AuthenticationManagerBuilder auth)
.
When should I override the configure(AuthenticationManagerBuilder auth) from Spring Security in a Spring Boot app?
You should override if you have different authorization/authentication requirements and would you like to plugin your own authentication provider to satisfy the requirement or add any built in provider like ldap and in memory providers. You can also do it directly using http security bean shown below.
All the authentication providers are added to Provider Manager
and are tried until one is found.
By default without providing anything ( i.e. without user details service or without overriding authentication manager ) you would have the default global authentication manager with auto configured user details manager ( i.e user password InMemoryUserDetailsManager
implementation as configured in UserDetailsServiceAutoConfiguration
auto configuration ).
So when you provide user details service application bean the auto configuration backs off and now your global authentication manager now is configured with the provided bean.
More details here
Here
is the good explanation how it all comes together.
I would also like to expand little bit more on spring security authentication manager in general which is very easy to overlook.
As I previously noted there is global authentication manager and local authentication managers. There is special care to be taken when configuring each if needed.
This is explained in the java doc for the global authentication manager annotation.
The EnableGlobalAuthentication annotation signals that the annotated class can be used to configure a global instance of AuthenticationManagerBuilder. For example:
@Configuration @EnableGlobalAuthentication public class MyGlobalAuthenticationConfiguration { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { auth.inMemoryAuthentication().withUser("user").password("password").roles("USER") .and().withUser("admin").password("password").roles("USER", "ADMIN");}}
Annotations that are annotated with EnableGlobalAuthentication also signal that the annotated class can be used to configure a global instance of AuthenticationManagerBuilder. For example:
@Configuration @EnableWebSecurity public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { auth.inMemoryAuthentication().withUser("user").password("password").roles("USER") .and().withUser("admin").password("password").roles("USER", "ADMIN"); } // Possibly overridden methods ... }
The following annotations are annotated with EnableGlobalAuthentication EnableWebSecurity EnableWebMvcSecurity EnableGlobalMethodSecurity
Configuring AuthenticationManagerBuilder in a class without the EnableGlobalAuthentication annotation has unpredictable results.
EnableGlobalAuthentication
imports configuration AuthenticationConfiguration
responsible for setting up the default configuration for global authentication manager.
AuthenticationConfiguration
configures two key pieces to make the authentication manager - user details and authentication provider.
User details is configured using InitializeUserDetailsBeanManagerConfigurer
and authentication provider is configured using InitializeAuthenticationProviderBeanManagerConfigurer
. Both of required beans are looked up in application context - that is how your user detail service is registered with global authentication manager.
GlobalMethodSecurityConfiguration
and WebSecurityConfigurerAdapter
are consumers of global authentication managers.
WebSecurityConfigurerAdapter
can be used to create and configure local authentication manager (add new authentication providers) and also typically used to have different authentication/authorization requirements in application like mvc vs rest and public vs admin endpoints.
With spring security alone @EnableWebSecurity
triggers the above flow as part of spring security filter chain set up. With spring boot the same flow is triggered by spring security auto configuration.
In spring security 5.4 version you can define http security as beans without needing to extend WebSecurityConfigurerAdapter class. Spring boot will have support for this in 2.4.0 release. More details here
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception
{
http
.authenticationProvider(custom authentication provider)
.userDetailsService( custom user details service)
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With