'Spring Boot Custom Authentication Provider with Java Configuration not working
I am trying to setup a REST based web application, where the frontend is using Reactjs and the backend is using Spring Boot. I am also trying to setup a custom authentication provider, and this is where my problems start. When trying to test the login API call, the CustomAuthenticationProvider is never called, and instead the default DaoAuthenticationProvider is used. This causes the login to report "Bad credentials".
I have upload a small sample application to github: spring-boot-auth-demo
To test the login API I use the following curl:
curl -H "Content-Type: application/json" -X POST -d '{"username":"admin","password":"admin"}' http://localhost:8080/api/users/login
The CustomAuthenticationProvider does a simple username/password check and returns an UsernamePasswordAuthenicationToken object.
package no.bluebit.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
public CustomAuthenticationProvider() {
logger.info("*** CustomAuthenticationProvider created");
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.getName().equals("admin") && authentication.getCredentials().equals("admin")) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), grantedAuths);
} else {
return null;
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
The CustomAuthenticationProvider is wired up using the SecurityConfiguration class. When stepping through the code, I can see that the CustomAuthenicationProvider is not in the list of providers used to authenticate the incoming request.
package no.bluebit.demo;
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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(this.customAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/users/login").permitAll() // Permit access for all to login REST service
.antMatchers("/").permitAll() // Neccessary to permit access to default document
.anyRequest().authenticated().and() // All other requests require authentication
.httpBasic().and()
.logout().and()
.csrf().disable();
}
}
Why is this not working?
Solution 1:[1]
Try to add on header http this thinks:
Example:
const headers = new HttpHeaders();
headers.set('Access-Control-Allow-Origin', '*');
headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT');
headers.set('Access-Control-Allow-Headers', 'Authorization, Content-Type, Accept, x-
requested-with, Cache-Control');
headers.set('Content-Type', 'application/json');
this.http.post('http://localhost:8081/loginAngular',
JSON.stringify({user: 'sdasd', password: 'dasdasd', estado: 'dasdasd', idUsuario: 1, resultado: 'asdasd'}) ,
{headers: new HttpHeaders().set('Content-Type', 'application/json')}).subscribe(data => {
console.log(' Data: ' + data);
});
I made this app with spring security and angular! Front: https://github.com/nicobassart/angularforHidra Back: https://github.com/nicobassart/hidra_server
Solution 2:[2]
Look at the AuthenticationProvider class (respectively it's java doc)
The method authenticate expects to :
* Performs authentication with the same contract as
* {@link org.springframework.security.authentication.AuthenticationManager#authenticate(Authentication)}
* @return a fully authenticated object including credentials. May return
* <code>null</code> if the <code>AuthenticationProvider</code> is unable to support
* authentication of the passed <code>Authentication</code> object. In such a case,
* the next <code>AuthenticationProvider</code> that supports the presented
* <code>Authentication</code> class will be tried.
If you return null, then the next AuthenticationProvider will be called, which is the defaut one.
I am not sure this is the problem, but this might be something. Try to throw BadCredentialsException, as the AuthenticationManager class tells you to do :
* <li>A {@link BadCredentialsException} must be thrown if incorrect credentials are
* presented. Whilst the above exceptions are optional, an
* <code>AuthenticationManager</code> must <B>always</B> test credentials.</li>
Solution 3:[3]
You have to set the credentials in some other way. Try to see a working example of Username Password token. But your "authenticate" function needs to be the one that sets the credentials.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Nico |
Solution 2 | Catalin Stan |
Solution 3 | Dexter |