Spring Security returns "Bad Credentials" with no exception on LDAP based authentication - spring-security

I am trying to create Sprint Boot, Spring Security 6, LDAP Server (external not embedded) based authentication application. When I spin up the app and provide the username (uid) and password on the login form I get a "Bad Credentials" message displayed on the UI. There are no exceptions reported in the application log. I do not understand what is causing "Bad Credentials" message to be displayed. Any pointers are much appreciated.
This is what my config file looks like
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeHttpRequests().anyRequest().fullyAuthenticated()
.and()
.formLogin();
httpSecurity.authenticationProvider(ldapAuthenticationProvider());
return httpSecurity.build();
}
#Bean
LdapAuthenticationProvider ldapAuthenticationProvider() {
return new LdapAuthenticationProvider(authenticator());
}
#Bean
BindAuthenticator authenticator() {
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", contextSource());
BindAuthenticator authenticator = new BindAuthenticator(contextSource());
authenticator.setUserSearch(search);
return authenticator;
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource dsCtx = new DefaultSpringSecurityContextSource("ldap://localhost:389/dc=example,dc=com");
dsCtx.setUserDn("cn=admin,dc=example,dc=com");
dsCtx.setPassword("password");
return dsCtx;
}
}
When I try to find user using ldapsearch command I do get the user info
MacBook-Pro:springsecuritywithldapdemo$ ldapsearch -LLL -x -H ldap:// -t -b "dc=example,dc=com" "uid=jsmith1"
dn: uid=jsmith1,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
description: John Smith from Accounting. John is the project manager of the b
uilding project, so contact him with any questions.
cn: John Smith
sn: Smith
uid: jsmith1
userPassword:: anNtaXRoMTIz
I have gone through many of the search results returned by google on different searches, most of them have used an older version of Spring Security or have used JDBC authentication with Spring Security 6.
I have referred to the youtube tutorials to see if I am doing anything wrong but doesn't look like.

first of all I wanted to thank you because based on your solution I was able to solve the same problem in my application. Compared to your solution the only things I have changed are:
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch("cn=users,cn=accounts,dc=example,dc=com",
"(uid={0})",
contextSource());
and so in your case you should try:
FilterBasedLdapUserSearch search = new FilterBasedLdapUserSearch("ou=people,dc=example,dc=com", "(uid={0})", contextSource());
and then I also had to make the following changes:
DefaultSpringSecurityContextSource dsCtx = new DefaultSpringSecurityContextSource(
"ldap://localhost:389");
dsCtx.setUserDn("uid=admin,cn=users,cn=accounts,dc=example,dc=com");
which in your case I think is almost identical since I think you also have a user with uid=admin. I hope it will solve your problem, as it did for me

Related

Spring Security Custom Login Fallback

Can someone please point me to some resources that can guide me on how I can implement a custom spring security authentication that first test the credentials of the found user on an ldap server first if the field for the ldap username exists for that user, and failing to authenticate (either because the ldap username doesn't exist or the password given doesn't authenticate the username on the server) there would attempt to authenticate against the local password kept hashed in the local database for the user.
Thank you.
It seems there aren't too many good answers to that specific question available.
While I have not stood up a full working LDAP example, there should be a good start to that part in the ldap sample that #Marcus linked in the comments. Having said that, you can easily register two authentication providers in the order you want, with the default DaoAuthenticationProvider being second:
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
return http
.authorizeRequests(authorizeRequests -> authorizeRequests
.anyRequest().authenticated()
)
.formLogin(withDefaults())
.authenticationProvider(ldapAuthenticationProvider())
.authenticationProvider(daoAuthenticationProvider())
.build();
// #formatter:on
}
#Bean
public AuthenticationProvider ldapAuthenticationProvider() {
LdapContextSource contextSource = null; // TODO See sample for example
return new LdapAuthenticationProvider(new BindAuthenticator(contextSource));
}
#Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
return authenticationProvider;
}
#Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.builder()
.username("user")
.password("{noop}password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
}
You will obviously want to provide your own user details which uses a database instead of in-memory. The username-password sample would get you started with that, and you can replace the MapCustomUserRepository in that example with e.g. a spring data #Repository.

Keycloak with Spring 'Invalid credentials' after login

I'm having no luck in setting up a simple Spring gateway + oauth2 client with Keycloak standalone. The keycloack part of it works fine. Wireshark shows the token correctly generated.
The gateway security config is as follows. I'm still not sure whether there is a need to permitAll() the login callback url. Some guides suggest that it should be the case, others dont. I suspect the oauth provider manages that part behind the scenes. Nonetheless, with or without permitAll for the "/login/*" path, the result remains the same.
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(e -> e.anyExchange().authenticated());
http.oauth2Login(Customizer.withDefaults());
http.csrf().disable();
return http.build();
}
}
After login the redirect to https://localhost:9000/login seems incorrect, it should retry the original url, say https://localhost:9000/test-service/v1/listall/
EDIT
In order to rule out any misconfigurations, even tried a simplest possible gateway and api resource (un-authenticated) and setup simplest possible relam in keyclock. The results haven't changed :( There are dozens of articles out there doing the exact same thing.
Any pointers, ideas?
Many Thanks
Even I have completed all configurations including "creating a client", "creating a scope" and creating a "user" with this scope. I have encountered this issue again.
The only solution worked for me is adding scope :openid to application.yaml.
You can refer application.yaml Security OAuth config from here:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://${keycloak.base.url}/auth/realms/${realm}
client:
registration:
gateway:
provider: keycloak
client-id: ${client id}
client-secret:${client secret}
scope: openid
provider:
keycloak:
user-name-attribute: preferred_username
issuer-uri: https://${keycloak.base.url}/auth/realms/${realm}
According to offical doc(found at https://www.keycloak.org/docs/latest/securing_apps/#_spring_security_adapter), you should use
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
I figured it out, it was an incorrect user-name-attribute. The correct value is
user-name-attribute: preferred_username
For some reason, I had it set to preferred_name. It would save a lot of debug-time if only spring oauth writes the actual error instead of a generic invalid_grant.
First of all, for anyone troubleshooting Spring Security, I recommend enabling debug logging by setting the logging level in your application.properties or application.yml file.
application.properties format:
logging.level.org.springframework.security=DEBUG
application.yml format:
logging:
level:
org:
springframework:
security: DEBUG
I was having a similar issue when using OAuth2 along with Spring Session. Even after authenticating successfully with Keycloak, I would get this login error whenever my Spring Session had expired.
I do not condone messing around with authentication flows, but I was able to resolve this issue by setting my own authentication entry point on the ServerHttpSecurity:
http.exceptionHandling().authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/keycloak"));
I then had to handle any requests to the "/login" page in my Controller. For me, I just redirect to my default landing page for a root request:
#Controller
#RequestMapping("/")
public class RootController {
#GetMapping({"", "login"})
public Mono<String> index() {
return Mono.just("redirect:/myLandingPage");
}
}
Somehow adding the "scope=openid" in my applications.properties works fine for me
spring.security.oauth2.client.registration.spring-cloud-client.scope=openid

Spring security: ldap configuration

I have a trouble with configuring LDAP authentication with Spring.
Using LDAP Apache Directory Studio I have following working connection to LDAP Server:
Bind DN or USER: cn=HIDDEN_USERNAME,OU=HIDDEN_OU1,OU=HIDDEN2,OU=Admin,DC=MY_COMPANYNAME,DC=COM
Authorization ID: SASL PLAIN only
Bind Password: ******
Using this connection, I can find my account under root:
Root DSE/DC=MY_COMPANYNAME,DC=COM/OU=User Accounts/OU=Enabled Users/OU=Consultants/CN=MySurname My Name
Right click on my account gives following values:
DN: CN=MySurname MyName,OU=Consultants,OU=Enabled Users,OU=User Accounts,DC=MY_COMPANYNAME,DC=COM
URL: ldap://IP_ADRESS:389/CN=MySurname%20MyName,OU=Consultants,OU=Enabled%20Users,OU=User%20Accounts,DC=MY_COMPANYNAME,DC=COM
I am going to configure WebSecurityConfigurerAdapter in order to get authentication via ldap server in the following way:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userDnPatterns("CN={0},OU=Consultants,OU=Enabled Users,OU=User Accounts,DC=MY_COMPANYNAME,DC=COM")
.contextSource()
.url("ldap://IP_ADRESS:389/")
.managerDn("HIDDEN_USERNAME")
.managerPassword("*****")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
I tried to set userDnPattern in many ways without result. What I am doing wrong?
Using the DN pattern you specify, your logon attempt would need to be made with user ID "MySurname MyName" (and the space may be an issue). The user provided logon ID string is inserted into the DN pattern you include above, and you'll be binding with
CN=MySurname MyName,OU=Consultants,OU=Enabled Users,OU=User Accounts,DC=MY_COMPANYNAME,DC=COM
Which matches what your fully qualified DN appears to be. If you want to be able to log on with your ID and not the surname/name string that makes up your CN, or if accounts which need to authenticate exist in multiple OU locations, userSearch may be preferable to DN patterns.
If you are authenticating against an Active Directory domain, you may be able to use {0}#domain.gTLD or DOMAIN{0} as the user pattern -- when a logon ID is supplied, these patterns form the userPrincipalName and sAMAccountName respectively.
In response to your comment above: Active Directory hides the password field and it cannot be read even by domain administrators.
I concur with the other user that for AD you need to use a user search filter and if you want to do it against the username you should use samaccountname={0}

Spring security has SimpleGrantedAuthority but hasRole isn't working

I have the following code...
private static Collection<? extends GrantedAuthority>
readAuthorities(DecodedJWT jwt) {
...
return authorities;
}
It shows an Admin role. However, when I try to access a site by...
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.antMatchers(ADMIN).hasRole(Role.ADMIN.getRoleName())
....
}
// Role.ADMIN.getRoleName() == "Admin"
But when I go the the site requiring the admin permission I get a 403.
What am I missing?
As it turns out, spring security automatically prepends "ROLE_" onto role security requirements for things like .hasRole() and #Secured. So, you have to prepend "ROLE_" onto the roles when you assign them to users. If this is confusing, let me know and I'll go into more depth with examples from my project, I've just been working on this project for the last 12 hours and don't have time right now.

Spring OAuth2 - There is no client authentication. Try adding an appropriate authentication filter

We have an application which is using spring-security-oauth2:1.0. I was trying to change it to a newer version, spring-security-oauth2:2.0.7.RELEASE. Some classes were removed, some package structure is changed, I managed to sort out all those things and I was able to start the server without any issue. But I am facing a strange issue here.
With OAuth2 - 1.0 version, when the user logs in we used to do a GET request on /oauth/token, For example :
http://localhost:8080/echo/oauth/token?grant_type=password&client_id=ws&client_secret=secret&scope=read,write&username=john#abc.com&password=password123
and It used to work just fine.
When I try the same thing, First of all I am not able to make a GET request because of the logic in TokenEndPoint.java
private Set<HttpMethod> allowedRequestMethods = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.POST));
#RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
I have tried to make a POST request same as above URL, but I get InsufficientAuthenticationException with the error message
There is no client authentication. Try adding an appropriate authentication filter
This is because of the following POST request controller in TokenEndpoint.java. When I debug, I see that principal is null.
#RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
//principal is null here
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
.............
}
I have an authentication filter and it worked well when I used version 1.0. This is the relevant prats of my config:
<authentication-manager xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<bean id="userDetailsService" class="com.hcl.nc.service.UserDetailsService">
<constructor-arg><ref bean="sessionFactory" /></constructor-arg>
</bean>
I always thought that the request will be authenticated by authentication-provider and goes to token-endpoint but that does not seem to be the correct flow. After debugging the application with version 2.0.7, now I really doubt my understanding about the flow.
Could somebody please explain why it worked in previous version and why it's not working now?
Do I have do to something different to get a OAuth token??
NOTE: I have already checked these questions : here, here, here. But I was not able to find the correct solution.
I don't know the previous version, but I know a bit about 2.0.7.
I suspect your problem is that your TokenEndpoint security tries to authenticate your clients against your user service.
The TokenEndpoint is protected by a BasicAuthenticationFilter. By default this filter would use an AuthenticationManager instance, which itself holds an AuthenticationProvider, which itself depends on an instance of UserDetailsService.
The trick is that this particular instance of UserDetailsService must be client based, not user based : that's why there is a ClientDetailsUserDetailsService, which adapts ClientDetailsService to UserDetailsService.
Normally all this stuff is already done by default when you use the framework's configuration classes AuthorizationServerConfigurerAdapter, #EnableAuthorizationServer, etc..
I had the same problem and my application.yml had this line:
servlet:
path: /auth
so the token address was: /auth/oauth/token
I remove the path from application.yml so the token path became:
/oauth/token
And everything works fine.
I hope this help
One of the problems of the following error, can be that authentication was not performed. I have encountered this problem with older implementation of Spring.
verify that:
TokenEndpoint -> postAccessToken method. Check if Principal is not null. If it is null it means that Basic Authroziation was not performed.
One of the solution to add filter was to use:
#Configuration
public class FilterChainInitializer extends AbstractSecurityWebApplicationInitializer {
}
More information about AbstractSecurityWebApplicationInitializer can be found in Spring docs
The problem can be because of opening all requests. You should remove it.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/**");
}
in my case, i found this config:
security.allowFormAuthenticationForClients(); // here
then post this
http://localhost:8081/sso/oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=Yk4Sum&redirect_uri=http://localhost:8082/sso-demo/passport/login
its works for me, try it
#Configuration
#EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(Oauth2Config.class);
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); // here
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { // #formatter:off
clients.inMemory()
.withClient("unity-client")
.secret("unity")
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
;
} // #formatter:on
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}
I am following this tutorial - Practical Guide to Building an API Back End with Spring Boot'. See https://www.infoq.com/minibooks/spring-boot-building-api-backend , But with the latest SpringBoot Version(2.7)
and I run into this problem:
org.springframework.security.authentication.InsufficientAuthenticationException: There is no client authentication. Try adding an appropriate authentication filter. at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:91) ~[spring-security-oauth2-2.3.5.RELEASE.jar:na]
My solution/fix was to annotate WebSecurityGlobalConfig with #EnableWebSecurity because in the original course this annotation was missing.
So adding this annotaiton has fixed the error for me.

Resources