Why is an InMemoryUserDetailsManager injected into this controller even though I've configured a JdbcUserDetailsManager elsewhere? - spring-boot

I'm trying to use Spring Security JDBC Authentication in a Spring Boot web app.
Here's the (much simplified, relevant) configuration:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import javax.sql.DataSource;
#Configuration
public class SecurityConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
}
Here's a controller:
import org.adventure.inbound.UserFormData;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/users")
public class UserController {
private final UserDetailsManager userDetailsManager;
public UserController(UserDetailsManager userDetailsManager) {
this.userDetailsManager = userDetailsManager;
}
#PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<Void> registerUser(UserFormData userFormData) {
userDetailsManager.createUser(
User
.withUsername(userFormData.getUsername())
.password(userFormData.getPassword())
.authorities(new SimpleGrantedAuthority("ROLE_USER"))
.build());
return ResponseEntity.created(null).build();
}
}
When I start the app, I can see that:
The AuthenticationManagerBuilder gets a JDBCUDM configured:
but perhaps some step is missing?
The InMemoryUserDetailsManager is instantiated with Spring Security's default user:
The Controller, when instantiated, receives an InMemoryUserDetailsManager:
This means that when I try to create a new user, it uses the InMemoryUDM instead of the JDBCUDM that I'd like to use. Why is that?
Working solution
We already configure the UserDetailsService through JdbcUserDetailsManagerConfigurer, but didn't expose it as a bean.
#Autowired
#Bean
public UserDetailsManager configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
return jdbcUserDetailsManagerConfigurer.getUserDetailsService();
}

In your configuration override the userDetailsServiceBean(), and let it call the super method and add the #Bean annotation to make the configured service available. This is also explained here in the Javadocs.
#Configuration
public class SecurityConfiguration extends WebSecutiryConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManagerConfigurer jdbcUserDetailsManagerConfigurer = auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema();
}
#Bean
public UserDetailsService userDetailsServiceBean()
throws java.lang.Exception {
return super.userDetailsServiceBean();
}
}
Also because you want to use your configuration instead of the Spring Boot one, you might need to add #EnableWebSecurity to disable the Spring Boot defaults.

Make your SecurityConfiguration extend from WebSecurityConfigurerAdapter, add #EnableWebSecurity and expose your JdbcUserDetailsManager as #Bean.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
#Bean
public JdbcUserDetailsManager userDetailsManager() {
return yourJdbcUserDetailsManager;
}
/// etc.
}

Related

Spring Boot 3 + Spring Security 6 => 403 Forbidden with "requestMatchers"

For several days now I have been trying to solve a problem with Spring Security 6. I've read almost all the spring documentation for Spring Security 6 and I watched several tutorials and just cannot see where the mistake is. I looked at the code under a magnifying glass:
WebSecurityConfigurer.class:
package com.transfer.market.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer {
private final String ADMIN;
private final String ADMIN_PASS;
private final String SUPER;
private final String SUPER_PASS;
#Autowired
public WebSecurityConfigurer(AppSecurityExternalConfig appSecurityExternalConfig) {
this.ADMIN = appSecurityExternalConfig.getUser().getAdmin();
this.ADMIN_PASS = appSecurityExternalConfig.getPassword().getAdmin();
this.SUPER = appSecurityExternalConfig.getUser().getSup();
this.SUPER_PASS = appSecurityExternalConfig.getPassword().getSup();
}
#Bean
public UserDetailsService users() {
UserDetails admin = User.builder()
.username(ADMIN)
.password(encoder().encode(ADMIN_PASS))
.roles("ADMIN")
.build();
UserDetails sup = User.builder()
.username(SUPER)
.password(encoder().encode(SUPER_PASS))
.roles("ADMIN", "DBA")
.build();
return new InMemoryUserDetailsManager(admin, sup);
}
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**").hasAnyRole("ADMIN", "DBA")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
.anyRequest().denyAll()
);
return http.build();
}
#Bean
public static BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
I have printed ADMIN, SUPER and their passwords to the console and they are for sure read correctly from the application.properties. So that is not the problem.
AppSecurityExternalConfig.class:
package com.transfer.market.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
#Data
#Configuration
#ConfigurationProperties(prefix = "config.security")
public class AppSecurityExternalConfig {
private User user;
private Password password;
#Data
#Component
#ConfigurationProperties(prefix = "user")
public static class User {
private String user;
private String admin;
private String sup;
}
#Data
#Component
#ConfigurationProperties(prefix = "password")
public static class Password {
private String user;
private String admin;
private String sup;
}
}
application.properties:
...
# Security:
config.security.user.admin=admin
config.security.password.admin=pass
config.security.user.sup=super
config.security.password.sup=pass
...
PlayerController.class:
#RestController
#Validated
public class PlayerController {
private final PlayerService playerService;
#Autowired
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#PostMapping("/api/players")
public ResponseEntity<Player> addSingle(#RequestBody #Valid Player player) {
return new ResponseEntity<>(playerService.addSingle(player), HttpStatus.CREATED);
}
...
It just keeps getting "403 Forbidden", but for all end points starting with "/resource" where they are .permitAll() it works and it's 200 OK. Why doesnt the other requestMatchers work? Please help.
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**")
.hasAnyRole("ADMIN", "DBA")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and
hasRole('DBA')"))
.anyRequest().denyAll());
return http.build();
}
You have to configured the basic authentication. Add the following statement in the SecurityFilterChain bean.
http.httpBasic();
i.e.
#Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth.requestMatchers("/resource/**").permitAll()
.requestMatchers("/api/**")
.hasAnyRole("ADMIN")
.requestMatchers("/db/**")
.access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
.anyRequest().denyAll());
http.httpBasic();
return http.build();
}

How to override SecurityFilterChain in Spring Boot context?

I am facing the issue which is not obvious to resolve just by reading the documentation. While migrating to Spring Boot v2.7.4 / Spring Security v5.7.3 I have refactored the configuration not to extend WebSecurityConfigurerAdapter and to look like below:
#Configuration
#EnableWebSecurity
public class CustomSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.
csrf().disable().
logout().disable().
authorizeRequests().anyRequest().permitAll();
return http.build();
}
}
The above method is called, however has no effect as SecurityFilterChain instance created by OAuth2SecurityFilterChainConfiguration is used instead (I see that from debug by inspecting the list of filter in the stack that has e.g. LogoutFilter which should be disabled by above configuration). Debug log:
2022-10-20 15:49:48.790 [main] o.s.b.a.s.DefaultWebSecurityCondition : Condition DefaultWebSecurityCondition on org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration matched due to AllNestedConditions 2 matched 0 did not; NestedCondition on DefaultWebSecurityCondition.Beans #ConditionalOnMissingBean (types: org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter,org.springframework.security.web.SecurityFilterChain; SearchStrategy: all) did not find any beans; NestedCondition on DefaultWebSecurityCondition.Classes #ConditionalOnClass found required classes 'org.springframework.security.web.SecurityFilterChain', 'org.springframework.security.config.annotation.web.builders.HttpSecurity'
2022-10-20 15:49:48.791 [main] a.ConfigurationClassBeanDefinitionReader : Registered bean definition for imported class 'org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration'
2022-10-20 15:49:48.792 [main] o.s.b.a.condition.OnBeanCondition : Condition OnBeanCondition on org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration$OAuth2SecurityFilterChainConfiguration#jwtSecurityFilterChain matched due to #ConditionalOnBean (types: org.springframework.security.oauth2.jwt.JwtDecoder; SearchStrategy: all) found bean 'jwtDecoderByJwkKeySetUri'
...
2022-10-20 15:49:49.082 [main] a.ConfigurationClassBeanDefinitionReader : Registering bean definition for #Bean method com.mycompany.CustomSecurityConfig.filterChain()
...
2022-10-20 15:49:52.276 [main] edFilterInvocationSecurityMetadataSource : Adding web access control expression [authenticated] for any request
2022-10-20 15:50:13.348 [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#33502cfe, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#729d1428, org.springframework.security.web.context.SecurityContextPersistenceFilter#7d0312a, org.springframework.security.web.header.HeaderWriterFilter#6ca97ddf, org.springframework.security.web.csrf.CsrfFilter#38f569d, org.springframework.security.web.authentication.logout.LogoutFilter#1104ad6a, org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter#74ab8610, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#7833407, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#66acaa54, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#115924ba, org.springframework.security.web.session.SessionManagementFilter#6a905513, org.springframework.security.web.access.ExceptionTranslationFilter#5749e633, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#49741e80]
...
2022-10-20 15:50:13.384 [main] edFilterInvocationSecurityMetadataSource : Adding web access control expression [permitAll] for any request
2022-10-20 15:50:17.641 [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter#4a0f4282, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#19d3f4fb, org.springframework.security.web.context.SecurityContextPersistenceFilter#99f75e4, org.springframework.security.web.header.HeaderWriterFilter#118c1faa, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#2b6ff016, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#5aefdb9e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#43cf97a8, org.springframework.security.web.session.SessionManagementFilter#da5b46f, org.springframework.security.web.access.ExceptionTranslationFilter#11267e87, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#7827cdfc]
Is it expected that the bean CustomSecurityConfig.filterChain participates in DefaultWebSecurityCondition evaluation and OAuth2SecurityFilterChainConfiguration.jwtSecurityFilterChain is not created. Or the issue with DefaultWebSecurityCondition is that the instance of WebSecurityConfigurerAdapter is not in the context anymore (as to issue #10822 it is deprecated)?
The suggestion to add #Order() annotation didn't work:
#Configuration
#EnableWebSecurity
public class CustomSecurityConfig {
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ...
as well as further attempts to exclude the autoconfiguration class like this:
#SpringBootApplication(excludeName = "org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration")
public class Application extends SpringBootServletInitializer { ...
failed probably due to issue #5427 with the following error
java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration
at org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.handleInvalidExcludes(AutoConfigurationImportSelector.java:222) ~[spring-boot-autoconfigure-2.7.4.jar!/:2.7.4]
This way also does not work:
#ComponentScan(excludeFilters = {#ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*OAuth2ResourceServerJwtConfiguration.*")})
public class Application extends SpringBootServletInitializer { ...
The documentation I read before posting:
Spring Security without the WebSecurityConfigurerAdapter
Spring Security: Upgrading the Deprecated WebSecurityConfigurerAdapter
Spring Security - How to Fix WebSecurityConfigurerAdapter Deprecated
Update
I have created a small Maven project that demonstrates the issue. After project is started, request the controller like this:
$ wget -nv -O - 'http://localhost:8080/spring/test'
Username/Password Authentication Failed.
As one can see, the custom configured SecurityFilterChain is not active because otherwise the access would be granted (as to antMatchers( "/**/test").permitAll()). ContextRefreshedEvent listener dumps two SecurityFilterChain instances (jwtSecurityFilterChain and filterChain), the priority of them is not possible to configure reliably.
As follows from issue #33103, the beans imported from XML via #ImportResource do not participate in Spring Boot autoconfiguration, hence beans scanning should be performed using annotation i.e. #SpringBootApplication(scanBasePackages = "some.package") – this basically solves the issue. –
dma_k
Nov 17, 2022 at 9:21
Thanks to this reply I found the solution.
But since we are using different auth method so it might not work for you. I am using DaoAuthenticationProvider from this tutorial. Then rewrite to a new version without using "WebSecurityConfigurerAdapter".
Hope this solution helps. I got tortured 3-4 hours finding solution.
I created a subpackage called "config", on the same level with "models, controller, service, etc"(or whatever you name them).
Then, manully import this package by "scanBasePackages" along with others.
#SpringBootApplication(scanBasePackages = {"com.example.test.controller", "com.example.test.model", "com.example.test.repo", "com.example.test.service","com.example.test.config"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Here are 3 rewritten files in my "config" folder. All rewrote from the tutorial mentioned above.
a.CustomUserDetails
package com.example.test.config;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.example.test.model.UserModel;
public class CustomUserDetails implements UserDetails {
//#Autowired no need cuz this is not a bean
private UserModel user;
public CustomUserDetails(UserModel user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public String getFullName() {
return user.getFirstName() + " " + user.getLastName();
}
}
b. CustomUserDetailsService
package com.example.test.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.example.test.repo.UserRepo;
import com.example.test.model.UserModel;
import org.springframework.stereotype.Service;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserModel user = userRepo.findByEmail(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user);
}
}
c. SecurityConfiguration
package com.example.test.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
public class SecurityConfiguration{
#Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests ((requests)->requests
.requestMatchers("/", "/register", "/try2Register").permitAll()
.anyRequest().authenticated()
)
.formLogin((form)->form
.usernameParameter("email")
.defaultSuccessUrl("/users")
.permitAll()
)
.logout((logout) -> logout
.logoutSuccessUrl("/").permitAll());
http.authenticationProvider(authenticationProvider());
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/images/**", "/js/**", "/webjars/**");
}
}

Using transactional in spring boot with hibernate

I am getting the error while using hibernate in spring boot application No qualifying bean of type TransactionManager' available
I am using the following config class:
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#org.springframework.context.annotation.Configuration
#EnableTransactionManagement
public class Config {
#Bean
public SessionFactory sessionFactory() {
Configuration configuration = new Configuration();
configuration.configure();
configuration.addAnnotatedClass(Ct.class);
configuration.addAnnotatedClass(St.class);
SessionFactory sessionFactory = configuration.buildSessionFactory();
return sessionFactory;
}
}
#RestController
public class RestAPIController {
#Autowired
private SessionFactory sessionFactory;
#PutMapping("/addS")
#Transactional
public void addSt(#RequestParam("cc") String cc,#RequestParam("st") String st) {
CC cc1= new CC();
CC.setCode(cc);
State state = new State(cc,st);
sessionFactory.getCurrentSession().save(state);
}
}
}
The main reason I added the #Transactional in the addSt method is due to error: The transaction was still an active when an exception occurred during Database.
So I turned to use spring boot for managing transactions. I am not sure what to do here.
--------------------UPDATED CODE--------------------
#Repository
public interface StateRepository extends CrudRepository<State, String> {}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;
#Service
#Transactional
public class StateService {
#Autowired
private StateRepository stateRepository;
public void save(State state) {
stateRepository.save(state);
}
public List<State> findAll() {
List<State> states = new ArrayList<>();
stateRepository.findAll().forEach(states::add);
return states;
}
}
For starters use proper layers and write a service and use JPA instead of plain Hibernate. If you want a Session you can always use EntityManager.unwrap to obtain the underlying Session.
#Service
#Transactional
public StateService {
#PersistenceContext
private EntityManager em;
public void save(State state) {
em.persist(state);
}
Use this service in your controller instead of the SessionFactory.
#RestController
public class RestAPIController {
private final StateService stateService;
RestAPIController(StateService stateService) {
this.stateService=stateService;
}
#PutMapping("/addS")
public void addSt(#RequestParam("cc") String cc, #RequestParam("st") String st) {
CC cc1= new CC();
CC.setCode(cc);
State state = new State(cc,st);
stateService.save(state);
}
}
Now ditch your Config class and restart the application.
NOTE
When using Spring Data JPA it is even easier, define a repository extending CrudRepository and inject that into the service instead of an EntityManager. (I'm assuming that Long is the type of primary key you defined).
public interface StateRepository extends CrudRepository<State, Long> {}
#Service
#Transactional
public StateService {
private final StateRepository states;
public StateService(StateRepository states) {
this.states=states;
}
public void save(State state) {
states.save(state);
}
}

PROBLEM WITH required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found

When launching with mvn spring-boot:run or even with gradle returns that issue.
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userDetailsService in com.ess.study.jwt.integration.config.SecurityConfig required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
The following candidates were found but could not be injected:
- Bean method 'inMemoryUserDetailsManager' in 'UserDetailsServiceAutoConfiguration' not loaded because #ConditionalOnMissingBean (types: org.springframework.security.authentication.AuthenticationManager,org.springframework.security.authentication.AuthenticationProvider,org.springframework.security.core.userdetails.UserDetailsService; SearchStrategy: all) found beans of type 'org.springframework.security.authentication.AuthenticationManager' authenticationManager
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' in your configuration.
Here are the main classes, all the requirements looks ok to me, I am using the org.springframework.boot release 2.0.6.RELEASE.RELEASE
package com.ess.study.jwt.integration.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
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.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
#Configuration
#EnableWebSecurity // Habilita la seguridad de Spring y consejos Spring Boot para aplicar todos los
// valores predeterminados sensibles
#EnableGlobalMethodSecurity(prePostEnabled = true) // Nos permite tener control de acceso a nivel de método.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${security.signing-key}")
private String signingKey;
#Value("${security.encoding-strength}")
private Integer encodingStrength;
#Value("${security.security-realm}")
private String securityRealm;
#Autowired
private UserDetailsService userDetailsService;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder(encodingStrength));
}
#Autowired
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic()
.realmName(securityRealm).and().csrf().disable();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary // Making this primary to avoid any accidental duplication with another token
// service instance of the same name
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
//Nuevo metodo de decodificación.
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
}
and:
package com.ess.study.jwt.integration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class StudySecuredApplication {
public static void main(String[] args) {
SpringApplication.run(StudySecuredApplication.class, args);
}
}
Using maven or gradle it returns the same issue. All annotations and packages names seems to be as required.
Thanks! :)
UserDetailsService is an interface and you have to create its implementation. Example:
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UsersRepository usersRepository;
#Transactional(readOnly = true)
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
try {
User user = usersRepository.findByLogin(login).get();
return new UserPrincipal(usersRepository.findByLogin(login).get());
} catch (NoSuchElementException e) {
throw new UsernameNotFoundException("User " + login + " not found.", e);
}
}
}

Spring-boot Digest authentication failure using Digest filter

I am new to this technology . I have trying to implement Digest Authentication for my Springboot application . I am getting below error while I am trying to call my application :There is no PasswordEncoder mapped for the id \"null\"","path":"/countryId/"}* Closing connection 0
curl command I am using to invoke : curl -iv --digest -u test:5f4dcc3b5aa765d61d8327deb882cf99 -d {"CountryCode": "INDIA"} http://localhost:9090/countryId/
Classes Details :
package com.sg.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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.UserCache;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.stereotype.Component;
#Component
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomDigestAuthenticationEntryPoint customDigestAuthenticationEntryPoint;
/*#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}*/
#Bean
public UserDetailsService userDetailsServiceBean()
{
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("test").password("{noop}password").roles("USER").build());
return manager;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/hello/**").permitAll().anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customDigestAuthenticationEntryPoint).and()
.addFilter(digestAuthenticationFilter());
}
//#Bean
DigestAuthenticationFilter digestAuthenticationFilter() throws Exception {
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
digestAuthenticationFilter.setAuthenticationEntryPoint(customDigestAuthenticationEntryPoint);
return digestAuthenticationFilter;
}
}
package com.sg.config;
import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
#Component
public class CustomDigestAuthenticationEntryPoint extends DigestAuthenticationEntryPoint {
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("Digest-Realm");
setKey("MySecureKey");
setNonceValiditySeconds(300);
super.afterPropertiesSet();
}
}
I have resolved the issue. Let me explain what went wrong first, in current Spring security, you can not use a plain text password, so have to keep some encrypting logic. But unfortunately Digest doesn't work with a encrypted password.
I have found a work around, instead using a Bean (Bycrypt), I have directly implemented PasswordEncoder interface, in a way, it should able to hold plain text password.
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
};
}

Resources