Spring Security: Can other beans access the global AuthenticationManagerBuilder? - java

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).

Related

SecurityConfiguration required bean of type BCryptPasswordEncoder

I tried looking at a couple similar questions however, seems they use different methods/class structures
I'm also using Lombok (not sure if this is an issue)
Here is the error:
Parameter 1 of constructor in com.example.javaspringboot.Security.SecurityConfiguration required a bean of type 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder' in your configuration.
The class it references is the following:
#Configuration #EnableWebSecurity #RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserDetailsService uds; // VERIFY LOGIN ATTEMPTS
private final BCryptPasswordEncoder bcpe; // ENCODING PASSWORDS
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(uds).passwordEncoder(bcpe);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http); //USES COOKIES LOOK MORE INTO THIS
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().anyRequest().permitAll();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception{ // NO IDEA
return super.authenticationManagerBean();
}
}
The only other class it is being used is in the main application class, as shown:
#SpringBootApplication
public class JavaSpringBootApplication {
// this runs on init
public static void main(String[] args) {
SpringApplication.run(JavaSpringBootApplication.class, args);
}
#Bean
PasswordEncoder passwordEncoder(){ // NEEDED TO ALLOW PASSWORD ENCODER INSIDE SECURITY
return new BCryptPasswordEncoder();
}
};
}
}
It's a little confusing considering the class it specifies already has #Bean outlined.
Any suggestions are appreciated.
In your main class, the return type is wrong (or you need to change the class in your security config).
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
private final BCryptPasswordEncoder bcpe;
...
}
Requires BCryptPasswordEncoder. However
public class JavaSpringBootApplication {
#Bean
PasswordEncoder passwordEncoder(){ // NEEDED TO ALLOW PASSWORD ENCODER INSIDE SECURITY
return new BCryptPasswordEncoder();
}
}
return PasswordEncoder.
So, either change return type of your bean to BCryptPasswordEncoder or autowire PasswordEncoder in your config.

There is no PasswordEncoder mapped for the id "null" in Spring Security

I am migrating from Spring Boot 1.5.12 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 even after using delegate {noop}:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null
Here is my code :
SecurityConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
public SecurityConfig() {
super();
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().and().authorizeRequests()
.antMatchers("/api/v1/**")
.authenticated().and().httpBasic();
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/v2/api-docs","/configuration/ui","/swagger-resources", "/configuration/security", "/webjars/**",
"/swagger-resources/configuration/ui","/swagger-resources/configuration/security",
"/swagger-ui.html", "/admin11/*", "/*.html", "/*.jsp", "/favicon.ico", "//*.html", "//*.css", "//*.js",
"/admin11/monitoring","/proxy.jsp");
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Oauth2AuthorizationServerConfig
public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private JdbcTokenStore jdbcTokenStore;
#Bean
public TokenStore tokenStore() {
return jdbcTokenStore;
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomTokenEnhancer converter = new CustomTokenEnhancer();
converter.setSigningKey("secret_api");
return converter;
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.pathMapping("/oauth/token", "/api/v1/oauth/token");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("app").secret("{noop}secret")
.authorizedGrantTypes("password", "authorization_code").scopes("read", "write")
.autoApprove(true).accessTokenValiditySeconds(0);
}
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Bean
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
}
CustomUserDetailsService
public interface CustomUserDetailsService extends UserDetailsService {
UserDetails getByMsisdn(String msisdn);
void initDummyUsers();
}
To solve this issue I tried the below questions from Stackoverflow:
Spring Boot PasswordEncoder Error
The Spring Security documentation is addressing your exact problem.
The following error occurs when one of the passwords that are stored
has no id as described in Password Storage Format.
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped
for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
The easiest way to resolve the error is to switch to explicitly
provide the PasswordEncoder that you 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.
So you should be able to fix this by explicitly providing the PasswordEncoder
// remember, its bad practice
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
Or even better supply a custom defined password encoder delegator:
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
All taken from the official spring security docs on password encoding.

Spring Security Oauth2 Authentication for Javascript WebApp

I'm currently developing a React WebApp with a Spring Boot REST API in the backend. I want to use OAuth2 to secure the API (and to improve my knowledge). I have however some questions regarding the correct authentication flow to use.
Since the frontend is using JavaScript I should not use flows that require a client secret.
The only flow that does not require a client secret in Spring is the Implicit Flow. With this flow however, Spring does not support refresh tokens. That means that after some time the user would get automatically logged out and needs to authorize the WebApp again.
Another option I saw was creating a client without a secret and then use the Authorization Code flow. But I have some doubts if this is the right way to go.
So my question basically: Which is the best OAuth2 flow to use with a Javascript frontend, when I don't want the user to be logged out after some time?
WebSecurityConfig
#Configuration
#EnableWebSecurity
#Import(Encoders.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder userPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(userPasswordEncoder);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().permitAll()
.and()
.authorizeRequests().antMatchers("/login", "/error**").permitAll()
.anyRequest().authenticated();
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
private static final String SECURED_READ_SCOPE = "#oauth2.hasScope('read')";
private static final String SECURED_WRITE_SCOPE = "#oauth2.hasScope('write')";
private static final String SECURED_PATTERN = "/api/**";
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher(SECURED_PATTERN).authorizeRequests().anyRequest().authenticated();
}
}
OAuth2Config
#Configuration
#PropertySource({"classpath:persistence.properties"})
#EnableAuthorizationServer
#Import(WebSecurityConfig.class)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("dataSource")
private DataSource dataSource;
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder oauthClientPasswordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public OAuth2AccessDeniedHandler oauthAccessDeniedHandler() {
return new OAuth2AccessDeniedHandler();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource("mykeys.jks"),
"mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykeys"));
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(oauthClientPasswordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
}
The Implicit flow is meant for JavaScript implementations according to the OAuth2 spec
Refresh tokens are not supported with the Implicit flow. The spring implementation is following the Oauth2 spec.
In case of a Javascript client implementations, tokens are stored on the client. When using refresh tokens, the refresh token needs to be persisted on the client in order to obtain a new access token in the future. If this is the case, you might as wel issue a long(er) lasting access token instead of a refresh token. When working client side, there is no advantage in using refresh token.

Provider order using AuthenticationManagerBuilder

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"));
}
}

Testing Spring Boot Security configuration

I've done a very simple demo app to try testing of Spring Boot security.
This is my App configuration
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#SpringBootApplication
public class DemoApplication extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityService securityService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(securityService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.csrf().disable();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
My UserDetailsService implementation accepts all users with password 'password' granted admin role to the 'admin' user.
#Service
public class SecurityService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection<GrantedAuthority> authorities;
if (username.equals("admin")) {
authorities = Arrays.asList(() -> "ROLE_ADMIN", () -> "ROLE_BASIC");
} else {
authorities = Arrays.asList(() -> "ROLE_BASIC");
}
return new User(username, "password", authorities);
}
}
And I finally created a simple test to check it:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
#WebAppConfiguration
public class DemoApplicationTests {
#Autowired
private AuthenticationManager authenticationManager;
#Test
public void thatAuthManagerUsesMyService() {
Authentication auth = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken("admin", "password")
);
assertTrue(auth.isAuthenticated());
}
}
I expected the test to pass, but I got a BadCredentialsException instead. After debugging I realized that the AuthenticationManager injected by Spring in the test is not the one I configured. While digging the object in the eclipse debugger I saw that the UserDetailsServer was an InMemoryUserDetailsManager.
I also checked that the configure() methods in DemoApplication are called. What am I doing wrong?
Per WebSecurityConfigurerAdapter api reference for authenticationManagerBean()
Override this method to expose the
AuthenticationManager from configure(AuthenticationManagerBuilder) to
be exposed as a Bean.
So just override authenticationManagerBean() in your WebSecurityConfigurerAdapter and expose it as a bean with #Bean.
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

Categories

Resources