I have a problem setting Spring Security up.
So to start with, I have a Configuration class, something like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity
#ComponentScan("com.boardviewer")
public class BoardviewerConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private BoardviewerSecurityService boardviewerSecurityService;
#Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
return resolver;
}
#Bean /* The "${props} can now be parsed before runtime with this bean declaration */
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
/* Spring Sec */
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider dao = new DaoAuthenticationProvider();
dao.setUserDetailsService(boardviewerSecurityService);
return dao;
}
#Bean
public ProviderManager providerManager() {
List<AuthenticationProvider> list = new ArrayList<AuthenticationProvider>();
list.add(daoAuthenticationProvider());
return new ProviderManager(list);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(daoAuthenticationProvider());
auth.userDetailsService(boardviewerSecurityService);
}
}
Basically some basic WebSecurity configs...
I'm not getting ANY errors, but I can browse around the site normally without any restrictions.
For instance, I have a controller with the #PreAuthorize annotation, and I get straight through it.
I'm running Spring Security 3.2.0 RC2 to be able to get an annotation configuration going... But so far no luck.
Is there any additional config in the web.xml needed? Or am I missing something?
Anyone got an example of a working Spring Security annotation config?
Also, I'm using hibernate to fetch User accounts etc, and my boardviewerSecurityService looks like this:
#Service
public class BoardviewerSecurityService implements UserDetailsService {
#Inject
private UserDAO userDAO;
#Inject
private BoardviewerTransformer transformer;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User u = userDAO.getByUsername(s);
if(u == null) {
throw new UsernameNotFoundException("Couldn't find a user with that username");
} else {
return transformer.userToSpringUser(u);
}
}
}
And the transformer simply remaps the entity to a Spring UserDetails User object (org.springframework.security.core.userdetails.User)
Am I missing something? (A part from setting up the login page and url interceptors? I thought I won't need those since I only want to control access on class / method level)
Would appreciate any help!
Regards
Related
I am migrating from Spring Boot 1.4.9 to Spring Boot 2.0 and also to Spring Security 5 and I am trying to do authenticate via OAuth 2. But I am getting this error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null
From the documentation of Spring Security 5, I get to know that
storage format for password is changed.
In my current code I have created my password encoder bean as:
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
However it was giving me below error:
Encoded password does not look like BCrypt
So I update the encoder as per the Spring Security 5 document to:
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Now if I can see password in database it is storing as
{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ
With that 1st error gone and now when I am trying to do authentication I am getting below error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null
To solve this issue I tried all the below questions from Stackoverflow:
Spring Boot PasswordEncoder Error
Spring Oauth2. Password encoder is not set in DaoAuthenticationProvider
Here is a question similar to mine but not answerd:
Spring Security 5 - Password Migration
NOTE: I am already storing encrypted password in database so no need to encode again in UserDetailsService.
In the Spring security 5 documentation they suggested you can handle this exception using:
DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)
If this is the fix then where should I put it? I have tried to put it in PasswordEncoder bean like below but it wasn't working:
DelegatingPasswordEncoder def = new DelegatingPasswordEncoder(idForEncode, encoders);
def.setDefaultPasswordEncoderForMatches(passwordEncoder);
MyWebSecurity class
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(HttpMethod.OPTIONS)
.antMatchers("/api/user/add");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
MyOauth2 Configuration
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("test")
.scopes("read", "write")
.authorities(Roles.ADMIN.name(), Roles.USER.name())
.authorizedGrantTypes("password", "refresh_token")
.secret("secret")
.accessTokenValiditySeconds(1800);
}
}
Please guide me with this issue. I have spend hours to fix this but not able to fix.
When you are configuring the ClientDetailsServiceConfigurer, you have to also apply the new password storage format to the client secret.
.secret("{noop}secret")
Add .password("{noop}password") to Security config file.
For example :
auth.inMemoryAuthentication()
.withUser("admin").roles("ADMIN").password("{noop}password");
For anyone facing the same issue and not in need of a secure solution - for testing and debugging mainly - in memory users can still be configured.
This is just for playing around - no real world scenario.
The approach used below is deprecated.
This is where I got it from:
source
reference, also mentioned in the source above
Within your WebSecurityConfigurerAdapter add the following:
#SuppressWarnings("deprecation")
#Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
Here, obviously, passwords are hashed, but still are available in memory.
Of course, you could also use a real PasswordEncoder like BCryptPasswordEncoder and prefix the password with the correct id:
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Whenever Spring stores the password, it puts a prefix of encoder in the encoded passwords like bcrypt, scrypt, pbkdf2 etc. so that when it is time to decode the password, it can use appropriate encoder to decode. if there is no prefix in the encoded password it uses defaultPasswordEncoderForMatches. You can view DelegatingPasswordEncoder.class's matches method to see how it works. so basically we need to set defaultPasswordEncoderForMatches by the following lines.
#Bean(name="myPasswordEncoder")
public PasswordEncoder getPasswordEncoder() {
DelegatingPasswordEncoder delPasswordEncoder= (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
BCryptPasswordEncoder bcryptPasswordEncoder =new BCryptPasswordEncoder();
delPasswordEncoder.setDefaultPasswordEncoderForMatches(bcryptPasswordEncoder);
return delPasswordEncoder;
}
Now, you might also have to provide this encoder with DefaultPasswordEncoderForMatches to your authentication provider also. I did that with below lines in my config classes.
#Bean
#Autowired
public DaoAuthenticationProvider getDaoAuthenticationProvider(#Qualifier("myPasswordEncoder") PasswordEncoder passwordEncoder, UserDetailsService userDetailsServiceJDBC) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsServiceJDBC);
return daoAuthenticationProvider;
}
Don't know if this will help anyone. My working WebSecurityConfigurer and OAuth2Config code as below:
OAuth2Config File:
package com.crown.AuthenticationServer.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
#Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("crown")
.secret("{noop}thisissecret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
WebSecurityConfigurer:
package com.crown.AuthenticationServer.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
#Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
final User.UserBuilder userBuilder = User.builder().passwordEncoder(encoder::encode);
UserDetails user = userBuilder
.username("john.carnell")
.password("password")
.roles("USER")
.build();
UserDetails admin = userBuilder
.username("william.woodward")
.password("password")
.roles("USER","ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
Here is the link to the project:
springboot-authorization-server-oauth2
The java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" error message arises when upgrading from Spring Security 4 to 5. Please refer to this Baeldung article as for the complete explanation and possible solutions.
You can read in the official Spring Security Documentation that for the DelegatingPasswordEncoder the general format for a password is: {id}encodedPassword
Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".
Id examples are:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
If you are fetching the username and password from the database,
you can use below code to add NoOpPassword instance.
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(adm).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
Where adm is a custom user object for my project which has getPassword() and getUsername() methods.
Also remember, to make a custom User POJO, you'll have to implement UserDetails interface and implements all of it's methods.
Hope this helps.
Spring Boot official documentation has provided a solution for this
The easiest way to resolve the error is to switch to explicitly providing the PasswordEncoder that your passwords are encoded with. The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct PasswordEncoder.
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean.
Alternatively, you can prefix all of your passwords with the correct id and continue to use DelegatingPasswordEncoder. For example, if you are using BCrypt, you would migrate ... more
#Configuration
#EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
return provider;
}
}
adding the below two annotation is fixed that issue
#Configuration
#EnableWebSecurity
juste add this bean to an annoted #configuration class.
#Configuration
public class BootConfiguration {
#Bean
#Primary
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
next go to your WebSecurity configuration class and call the password bean add it to authentication provider dont forgot set in your auth manager bean the authentication provider bean and final set the authentication manager bean in the SecurityFilterChain use the example below
#EnableWebSecurity
#Configuration
public class DefaultSecurityConfig {
#Autowire
private PasswordEncoder passwordEncoder
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userManager);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
#Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(authenticationProvider();
return authenticationManagerBuilder.build();
}
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated());
http.authenticationManager(authManager(http));
http.exceptionHandling(exceptions->exceptions
.accessDeniedHandler(accessDeniedHandler));
http.formLogin(formLogin-> formLogin.loginPage("/login")
.permitAll());
http.logout(logout-> logout
.permitAll()
return http.build();
}
}
I am migrating from Spring Boot 1.4.9 to Spring Boot 2.0 and also to Spring Security 5 and I am trying to do authenticate via OAuth 2. But I am getting this error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null
From the documentation of Spring Security 5, I get to know that
storage format for password is changed.
In my current code I have created my password encoder bean as:
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
However it was giving me below error:
Encoded password does not look like BCrypt
So I update the encoder as per the Spring Security 5 document to:
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Now if I can see password in database it is storing as
{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ
With that 1st error gone and now when I am trying to do authentication I am getting below error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null
To solve this issue I tried all the below questions from Stackoverflow:
Spring Boot PasswordEncoder Error
Spring Oauth2. Password encoder is not set in DaoAuthenticationProvider
Here is a question similar to mine but not answerd:
Spring Security 5 - Password Migration
NOTE: I am already storing encrypted password in database so no need to encode again in UserDetailsService.
In the Spring security 5 documentation they suggested you can handle this exception using:
DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)
If this is the fix then where should I put it? I have tried to put it in PasswordEncoder bean like below but it wasn't working:
DelegatingPasswordEncoder def = new DelegatingPasswordEncoder(idForEncode, encoders);
def.setDefaultPasswordEncoderForMatches(passwordEncoder);
MyWebSecurity class
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(HttpMethod.OPTIONS)
.antMatchers("/api/user/add");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
MyOauth2 Configuration
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("test")
.scopes("read", "write")
.authorities(Roles.ADMIN.name(), Roles.USER.name())
.authorizedGrantTypes("password", "refresh_token")
.secret("secret")
.accessTokenValiditySeconds(1800);
}
}
Please guide me with this issue. I have spend hours to fix this but not able to fix.
When you are configuring the ClientDetailsServiceConfigurer, you have to also apply the new password storage format to the client secret.
.secret("{noop}secret")
Add .password("{noop}password") to Security config file.
For example :
auth.inMemoryAuthentication()
.withUser("admin").roles("ADMIN").password("{noop}password");
For anyone facing the same issue and not in need of a secure solution - for testing and debugging mainly - in memory users can still be configured.
This is just for playing around - no real world scenario.
The approach used below is deprecated.
This is where I got it from:
source
reference, also mentioned in the source above
Within your WebSecurityConfigurerAdapter add the following:
#SuppressWarnings("deprecation")
#Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
Here, obviously, passwords are hashed, but still are available in memory.
Of course, you could also use a real PasswordEncoder like BCryptPasswordEncoder and prefix the password with the correct id:
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Whenever Spring stores the password, it puts a prefix of encoder in the encoded passwords like bcrypt, scrypt, pbkdf2 etc. so that when it is time to decode the password, it can use appropriate encoder to decode. if there is no prefix in the encoded password it uses defaultPasswordEncoderForMatches. You can view DelegatingPasswordEncoder.class's matches method to see how it works. so basically we need to set defaultPasswordEncoderForMatches by the following lines.
#Bean(name="myPasswordEncoder")
public PasswordEncoder getPasswordEncoder() {
DelegatingPasswordEncoder delPasswordEncoder= (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
BCryptPasswordEncoder bcryptPasswordEncoder =new BCryptPasswordEncoder();
delPasswordEncoder.setDefaultPasswordEncoderForMatches(bcryptPasswordEncoder);
return delPasswordEncoder;
}
Now, you might also have to provide this encoder with DefaultPasswordEncoderForMatches to your authentication provider also. I did that with below lines in my config classes.
#Bean
#Autowired
public DaoAuthenticationProvider getDaoAuthenticationProvider(#Qualifier("myPasswordEncoder") PasswordEncoder passwordEncoder, UserDetailsService userDetailsServiceJDBC) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsServiceJDBC);
return daoAuthenticationProvider;
}
Don't know if this will help anyone. My working WebSecurityConfigurer and OAuth2Config code as below:
OAuth2Config File:
package com.crown.AuthenticationServer.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
#Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("crown")
.secret("{noop}thisissecret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
WebSecurityConfigurer:
package com.crown.AuthenticationServer.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
#Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
final User.UserBuilder userBuilder = User.builder().passwordEncoder(encoder::encode);
UserDetails user = userBuilder
.username("john.carnell")
.password("password")
.roles("USER")
.build();
UserDetails admin = userBuilder
.username("william.woodward")
.password("password")
.roles("USER","ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
Here is the link to the project:
springboot-authorization-server-oauth2
The java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" error message arises when upgrading from Spring Security 4 to 5. Please refer to this Baeldung article as for the complete explanation and possible solutions.
You can read in the official Spring Security Documentation that for the DelegatingPasswordEncoder the general format for a password is: {id}encodedPassword
Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".
Id examples are:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
If you are fetching the username and password from the database,
you can use below code to add NoOpPassword instance.
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(adm).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
Where adm is a custom user object for my project which has getPassword() and getUsername() methods.
Also remember, to make a custom User POJO, you'll have to implement UserDetails interface and implements all of it's methods.
Hope this helps.
Spring Boot official documentation has provided a solution for this
The easiest way to resolve the error is to switch to explicitly providing the PasswordEncoder that your passwords are encoded with. The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct PasswordEncoder.
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean.
Alternatively, you can prefix all of your passwords with the correct id and continue to use DelegatingPasswordEncoder. For example, if you are using BCrypt, you would migrate ... more
#Configuration
#EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Bean
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
return provider;
}
}
adding the below two annotation is fixed that issue
#Configuration
#EnableWebSecurity
juste add this bean to an annoted #configuration class.
#Configuration
public class BootConfiguration {
#Bean
#Primary
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
next go to your WebSecurity configuration class and call the password bean add it to authentication provider dont forgot set in your auth manager bean the authentication provider bean and final set the authentication manager bean in the SecurityFilterChain use the example below
#EnableWebSecurity
#Configuration
public class DefaultSecurityConfig {
#Autowire
private PasswordEncoder passwordEncoder
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userManager);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
#Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(authenticationProvider();
return authenticationManagerBuilder.build();
}
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated());
http.authenticationManager(authManager(http));
http.exceptionHandling(exceptions->exceptions
.accessDeniedHandler(accessDeniedHandler));
http.formLogin(formLogin-> formLogin.loginPage("/login")
.permitAll());
http.logout(logout-> logout
.permitAll()
return http.build();
}
}
I'm building an application using Spring Data Rest, Spring Boot and Spring Security. I need to use #Secured annotations on methods and I've configured Spring Security in the following way:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #formatter:off
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.securityContext().securityContextRepository(securityContextRepository())
.and()
.exceptionHandling()
.accessDeniedPage(RestPath.Errors.ROOT + RestPath.Errors.FORBIDDEN)
.and()
.csrf().disable();
}
// #formatter:on
#Bean
public SecurityContextRepository securityContextRepository() {
return new ApiUserSecurityContextRepository();
}
#Bean
public UserDetailsService userDetailsService() {
return new ApiUserDetailsService();
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Collections.singletonList(authenticationProvider()));
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This type of configuration works well for regular MVC controllers and returns 403 when I try to access them. For example, the following controller security works:
#ResponseBody
#RequestMapping(value = RestPath.Configs.SLASH_TEST, method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#Secured({"ROLE_USER"})
public ResponseEntity test(#RequestParam(value = RestParam.DB_TEST, required = false) final boolean dbTest) throws ApplicationAvailabilityException {
final AppTestData appTestData = configService.testAppAvailability(dbTest);
return ResponseEntity.ok(projectionFactory.createProjection(AppTestProjection.class, appTestData));
}
However, when I try to use #Secured annotation over a rest repository - it does NOT, e.g.:
#RepositoryRestResource(collectionResourceRel = Shop.COLLECTION_NAME, path = RestResourceRel.SHOPS, excerptProjection = StandardShopProjection.class)
#Secured({"ROLE_USER"})
public interface RestShopRepository extends MongoRepository<Shop, String> {
#Secured({"ROLE_ADMIN"})
#Override
Shop findOne(String s);
}
ApiUserSecurityContextRepository is getting called for both of the methods, but only a custom MVC controller is get to the end of chain and I can check that it accesses vote() method in RoleVoter class for granting access.
As an example, I've checked Spring Data Rest + Spring Security sample, so #Secured or #PreAuthorize annotations should work with Spring Data Rest. Any ideas why they don't work?
Finally resolved the issue. The problem was in the following, I had another ShopRepository in different application module, which was not annotated with #RepositoryRestResource and it was the one which was used when accessing it using REST.
The following line of configuration in custom RepositoryRestConfigurerAdapter fixed the exploration of repositories which need to be exposed, so only annotated ones are exposed now:
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
After that I could not access the resource at all using REST, so I've figured out that it is not visible to Spring. I just had to enable Mongo repositories on API level with annotation #EnableMongoRepositories.
I'm using Spring Security 4.0.1 and want to use multiple authentication providers for authentication using Java-based configuration. How do I specify the provider order?
I was hoping to use AuthenticationManagerBuilder, since that's what WebSecurityConfigurerAdapter.configureGlobal() exposes, but I don't see any way to specify the order. Do I need to create a ProviderManager manually?
Update: Here's a problem clarification based on Arun's answer. The specific providers I want to use are ActiveDirectoryLdapAuthenticationProvider and DaoAuthenticationProvider for a custom UserService.
Ultimately I'd like to authenticate against the DaoAuthenticationProvider first and the ActiveDirectoryLdapAuthenticationProvider second.
The AD provider involves a call to AuthenticationManagerBuilder.authenticationProvider() but the DAO provider involves calling AuthenticationManagerBuilder.userService(), which creates a DaoAuthenticationProvider around the user service behind the scenes. Looking at the source code, it doesn't directly place a provider in the provider list (it creates a configurer), so Arun's answer isn't working for me here.
I tried creating the DaoAuthenticationProvider manually and passing it to authenticationProvider(). It didn't impact the order.
I tried an objectPostProcessor inside the configure method and it worked. Not sure if this is what you want:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(new CustomAuthenticationProvider(this.dataSource));
auth.objectPostProcessor(new ObjectPostProcessor<Object>() {
#Override
public <O> O postProcess(O object) {
ProviderManager providerManager = (ProviderManager) object;
Collections.swap(providerManager.getProviders(), 0, 1);
return object;
}
});
}
This is the configure method that goes on your WebSecurityConfigurerAdapter inherited class.
The reason for the object post processor is that we need to wait the AuthenticationManagerBuilder to actually build the object before we can access and change the order of the providers list.
Hope it helps.. let me know if you have any questions.
There is no explicit ordering provision. The order of invocation will be the order in which you have provided your AuthenticationProviderto AuthenticationManagerBuilder.authenticationProvider(). Refer here for xml configuration. The same should apply for java config as well.
For eg
auth.authenticationProvider(getAuthenticationProvider2());
auth.authenticationProvider(getAuthenticationProvider1());
will result in the following order of invocation AuthenticationProvider2,AuthenticationProvider1
and
auth.authenticationProvider(getAuthenticationProvider1());
auth.authenticationProvider(getAuthenticationProvider2());
will result in the following order of invocation AuthenticationProvider1,AuthenticationProvider2
I had exactly the same problem in my Spring 5 application, but in my case creating DaoAuthenticationProvider helped. Here's my code (I omitted a lot of it and pasted the most important one).
...
import javax.annotation.PostConstruct;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.authenticationManagerBuilder = authenticationManagerBuilder;
this.userDetailsService = userDetailsService;
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#PostConstruct
public void init() {
try {
authenticationManagerBuilder.authenticationProvider(userDetailsAuthenticationProvider());
if (isSsoEnabled()) {
authenticationManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
if (isKerberosEnabled()) {
authenticationManagerBuilder
.authenticationProvider(kerberosServiceAuthenticationProvider());
}
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
#Bean
public DaoAuthenticationProvider userDetailsAuthenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new CustomPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
...
}
#Override
protected void configure(HttpSecurity http) throws Exception {
...
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider(getAdDomain(), getAdServer());
provider.setUserDetailsContextMapper((UserDetailsContextMapper) userDetailsService);
return provider;
}
private boolean isSsoEnabled() {
return Boolean.parseBoolean(ConfigurationFileUtils.getConfigurationProperty("security.use-sso"));
}
private boolean isKerberosEnabled() {
return isSsoEnabled() && Boolean.parseBoolean(ConfigurationFileUtils.getConfigurationProperty("security.use-kerberos"));
}
}
I have set up the boilerplate spring security Configurer:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...setting up security for routes, etc.
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// here I have access to the AuthenticationManagerBuilder
// I can associate it with my datasource, set the password encoder, etc.
JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
userDetailsService.setDataSource(datasource);
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
auth.jdbcAuthentication().dataSource(datasource);
}
But what I want is to be able to access that AuthenticationManagerBuilder from another bean like this:
#Service
public class MyUserService {
#Autowired
AuthenticationManagerBuilder builder;
public void createUser(...) {
//use builder here...
JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
userDetailsService.setDataSource(datasource);
PasswordEncoder encoder = new BCryptPasswordEncoder();
builder.userDetailsService(userDetailsService)
.passwordEncoder(encoder);
builder.jdbcAuthentication().dataSource(datasource);
userDetailsService.createUser(new User(...));
}
Is there any way to access from other beans the same AuthenticationManagerBuilder instance that is automatically passed to the configure() method?
The AuthenticationManagerBuilder is really only meant for building your authentication objects (i.e. UserDetails, AuthenticationProvider, AuthenticationManager). It is not intended to be used within the application itself.
Instead, I would recommend using the UserDetailsManager API. You can create a UserDetailsManager Bean, provide the UserDetailsManager to the AuthenticationManagerBuilder for creating the AuthenticationProvider & AuthenticationManager, then you can use the UserDetailsManager directly within your code.
Something like this:
#EnableWebMvcSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService uds) throws Exception {
auth
.userDetailsService(uds)
.passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
public UserDetailsManager udm(DataSource dataSource) {
JdbcUserDetailsManager udm = new JdbcUserDetailsManager();
udm.setDataSource(dataSource);
return udm;
}
}
#Service
public class MyUserService {
#Autowired
UserDetailsManager udm;
public void createUser(...) {
//use builder here...
udm.createUser(new User(...));
}
}
One thing to note is that we leverage the global instance of AuthenticationManagerBuilder. In order to ensure the configureGlobal method is invoked before building the AuthenticationProvider and AuthenticationManager you need to have EnableGlobalAuthentication, EnableWebMvcSecurity or EnableWebSecurity annotations on the Configuration class (our example already does this).