Authentication with Spring-Security via Active Directory LDAP - spring

I can't authenticate using a real active directory, let me explain better I tried to authenticate using the example proposed by spring.io without problem where a internal service is started without any problem.
reference https://spring.io/guides/gs/authenticating-ldap/
I tried to modify the code below by inserting the configuration of my active directory without success. Can you kindly guide me or show me a real case where a true connection is made without using internal services like those in the examples? I looked on the net but found everything similar to the official example without any real case
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new LdapShaPasswordEncoder())
.passwordAttribute("userPassword");
}
Error show:
Uncategorized exception occured during LDAP processing; nested exception is javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0907C2, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580

Yeah, authentication via LDAP that's too painful. In order to be able to perform authentication to AD you need to use the ActiveDirectoryLdapAuthenticationProvider.
Here is the working sample:
#Override
protected void configure(AuthenticationManagerBuilder auth) {
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://localhost:8389");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(adProvider);
}
And to save your time just read the following, that's really important:
AD authentication doc

I found a sample over here, which was useful:
https://github.com/sachin-awati/Mojito/tree/master/webapp/src/main/java/com/box/l10n/mojito/security
You can optionally implement UserDetailsContextMapperImpl which overrides mapUserFromContext to create the UserDetails object if the user is not found during the Active Directory lookup - loadUserByUsername.
#Component
public class UserDetailsContextMapperImpl implements UserDetailsContextMapper {
#Override
public UserDetails mapUserFromContext(DirContextOperations dirContextOperations, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails userDetails = null;
try {
userDetails = userDetailsServiceImpl.loadUserByUsername(username);
} catch (UsernameNotFoundException e) {
String givenName = dirContextOperations.getStringAttribute("givenname");
String surname = dirContextOperations.getStringAttribute("sn");
String commonName = dirContextOperations.getStringAttribute("cn");
userDetails = userDetailsServiceImpl.createBasicUser(username, givenName, surname, commonName);
}
return userDetails;
}
Ensure you are using the ActiveDirectoryLdapAuthenticationProvider spring security class as Active Directory has its own nuances compared to other LDAP servers. You'll probably need to be using the #EnableGlobalAuthentication annotation in your security configuration class as you can have multiple AuthenticationManagerBuilders which confuses things a lot.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider("domain.com", "ldap://primarydc.domain.com:389");
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
auth.authenticationProvider(adProvider);
}
More details here:
https://github.com/spring-projects/spring-security/issues/4324
https://github.com/spring-projects/spring-security/issues/4571

Related

LDAP searching AD finds no entries

My company has Active Directory set up. I am making an application whose login system should be linked to that AD, so I set up Spring LDAP authentication to implement it.
But when I try to log in to the application, I get
Failed to locate directory entry for authenticated user: my.name
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
'DC=dev,DC=company,DC=corp'
My Spring Security code looks like this:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389/dc=dev,dc=company,dc=corp");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
One of the problems is that our AD structure is really branched out, and each of those organizational units has further subdivision:
Reading up on LDAP, I thought giving it only the path to the common parent folder should do recursive search through the tree and find the matching user, no matter what OU they are in, and indeed, if I do a dsquery from command prompt it does return the queried users, no matter where they are. However, that doesn't happen in Spring. I've read just about everything there is to find on the topic, and debugged for hours - but it seems my parameters are set correctly, yet I still get thrown the Exception.
My question would be - how to make Spring see that the user is, in fact, in Active Directory, without specifying the complete URL to that specific user (since there are dozens of different OUs)?
Found the solution. Sometimes, rereading the documentation for Xth time actually helps.
I changed the constructor call of the ActiveDirectoryLdapAuthenticationProvider from this
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389/dc=dev,dc=company,dc=corp");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
to this
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("company.hr", "ldap://10.23.1.1:389", "dc=dev,dc=company,dc=corp");
The third parameter in the constrcutor is in fact "rootDn", so that's the place where it should be passed, not as an extension of the URL.

Spring boot + Spring LDAP + Service Account + Bad Credentials

i am getting bad credentials exception when using active directory service account and the same code is working fine for user account.
And there is nothing wrong with service user credentials.
Please find my code below.
#Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
"abc.def.ghi", "ldap://abc.def.ghi:389");
auth.authenticationProvider(provider);
}
}
The userPrincipleName property is not inline with expected format username#domainname for that specific user account.

Add custom UserDetailsService to Spring Security OAuth2 app

How do I add the custom UserDetailsService below to this Spring OAuth2 sample?
The default user with default password is defined in the application.properties file of the authserver app.
However, I would like to add the following custom UserDetailsService to the demo package of the authserver app for testing purposes:
package demo;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Service;
#Service
class Users implements UserDetailsManager {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password;
List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
if (username.equals("Samwise")) {
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
password = "TheShire";
}
else if (username.equals("Frodo")){
auth = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_HOBBIT");
password = "MyRing";
}
else{throw new UsernameNotFoundException("Username was not found. ");}
return new org.springframework.security.core.userdetails.User(username, password, auth);
}
#Override
public void createUser(UserDetails user) {// TODO Auto-generated method stub
}
#Override
public void updateUser(UserDetails user) {// TODO Auto-generated method stub
}
#Override
public void deleteUser(String username) {// TODO Auto-generated method stub
}
#Override
public void changePassword(String oldPassword, String newPassword) {
// TODO Auto-generated method stub
}
#Override
public boolean userExists(String username) {
// TODO Auto-generated method stub
return false;
}
}
As you can see, this UserDetailsService is not autowired yet, and it purposely uses insecure passwords because it is only designed for testing purposes.
What specific changes need to be made to the GitHub sample app so that a user can login as username=Samwise with password=TheShire, or as username=Frodo with password=MyRing? Do changes need to be made to AuthserverApplication.java or elsewhere?
SUGGESTIONS:
The Spring OAuth2 Developer Guide says to use a GlobalAuthenticationManagerConfigurer to configure a UserDetailsService globally. However, googling those names produces less than helpful results.
Also, a different app that uses internal spring security INSTEAD OF OAuth uses the following syntax to hook up the UserDetailsService, but I am not sure how to adjust its syntax to the current OP:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private Users users;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(users);
}
}
I tried using #Autowire inside the OAuth2AuthorizationConfig to connect Users to the AuthorizationServerEndpointsConfigurer as follows:
#Autowired//THIS IS A TEST
private Users users;//THIS IS A TEST
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.accessTokenConverter(jwtAccessTokenConverter())
.userDetailsService(users)//DetailsService)//THIS LINE IS A TEST
;
}
But the Spring Boot logs indicate that the user Samwise was not found, which means that the UserDetailsService was not successfully hooked up. Here is the relevant excerpt from the Spring Boot logs:
2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9] o.s.s.a.dao.DaoAuthenticationProvider :
User 'Samwise' not found
2016-04-20 15:34:39.998 DEBUG 5535 --- [nio-9999-exec-9]
w.a.UsernamePasswordAuthenticationFilter :
Authentication request failed:
org.springframework.security.authentication.BadCredentialsException:
Bad credentials
What else can I try?
I ran into a similar issue when developing my oauth server with Spring Security. My situation was slightly different, as I wanted to add a UserDetailsService to authenticate refresh tokens, but I think my solution will help you as well.
Like you, I first tried specifying the UserDetailsService using the AuthorizationServerEndpointsConfigurer, but this does not work. I'm not sure if this is a bug or by design, but the UserDetailsService needs to be set in the AuthenticationManager in order for the various oauth2 classes to find it. This worked for me:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
Users userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// other stuff to configure your security
}
}
I think if you changed the following starting at line 73, it may work for you:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
You would also of course need to add #Autowired Users userDetailsService; somewhere in WebSecurityConfigurerAdapter
Other things I wanted to mention:
This may be version specific, I'm on spring-security-oauth2 2.0.12
I can't cite any sources for why this is the way it is, I'm not even sure if my solution is a real solution or a hack.
The GlobalAuthenticationManagerConfigurer referred to in the guide is almost certainly a typo, I can't find that string anywhere in the source code for anything in Spring.
My requirement was to get a database object off the back of the oauth2 email attribute. I found this question as I assumed that I need to create a custom user details service. Actually I need to implment the OidcUser interface and hook into that process.
Initially I thought it was the OAuth2UserService, but I've set up my AWS Cognito authentication provider so that it's open id connect..
//inside WebSecurityConfigurerAdapter
http
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(new CustomOidcUserServiceImpl());
...
public class CustomOidcUserServiceImpl implements OAuth2UserService<OidcUserRequest, OidcUser> {
private OidcUserService oidcUserService = new OidcUserService();
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser oidcUser = oidcUserService.loadUser(userRequest);
return new CustomUserPrincipal(oidcUser);
}
}
...
public class CustomUserPrincipal implements OidcUser {
private OidcUser oidcUser;
//forward all calls onto the included oidcUser
}
The custom service is where any bespoke logic can go.
I plan on implementing UserDetails interface on my CustomUserPrincipal so that I can have different authentication mechanisms for live and test to facilitate automated ui testing.
I ran into the same issue and originally had the same solution as Manan Mehta posted. Just recently, some version combination of spring security and spring oauth2 resulted in any attempt to refresh tokens resulting in an HTTP 500 error stating that UserDetailsService is required in my logs.
The relevant stack trace looks like:
java.lang.IllegalStateException: UserDetailsService is required.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:463)
at org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper.loadUserDetails(UserDetailsByNameServiceWrapper.java:68)
at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:103)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150)
You can see at the bottom that the DefaultTokenServices is attempting to refresh the token. It then calls into an AuthenticationManager to re-authenticate (in case the user revoked permission or the user was deleted, etc.) but this is where it all unravels. You see at the top of the stack trace that UserDetailsServiceDelegator is what gets the call to loadUserByUsername instead of my beautiful UserDetailsService. Even though inside my WebSecurityConfigurerAdapter I set the UserDetailsService, there are two other WebSecurityConfigurerAdapters. One for the ResourceServerConfiguration and one for the AuthorizationServerSecurityConfiguration and those configurations never get the UserDetailsService that I set.
In tracing all the way through Spring Security to piece together what is going on, I found that there is a "local" AuthenticationManagerBuilder and a "global" AuthenticationManagerBuilder and we need to set it on the global version in order to have this information passed to these other builder contexts.
So, the solution I came up with was to get the "global" version in the same way the other contexts were getting the global version. Inside my WebSecurityConfigurerAdapter I had the following:
#Autowired
public void setApplicationContext(ApplicationContext context) {
super.setApplicationContext(context);
AuthenticationManagerBuilder globalAuthBuilder = context
.getBean(AuthenticationManagerBuilder.class);
try {
globalAuthBuilder.userDetailsService(userDetailsService);
} catch (Exception e) {
e.printStackTrace();
}
}
And this worked. Other contexts now had my UserDetailsService. I leave this here for any brave soldiers who stumble upon this minefield in the future.
For anyone got UserDetailsService is required error when doing refresh token, and you confirm you already have UserDetailsService bean.
try add this:
#Configuration
public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter {
private UserDetailsService userDetailsService;
public GlobalSecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
This is my try and error, maybe not work for you.
By the way, if you give up "guess" which bean will pick by spring, you can extends AuthorizationServerConfigurerAdapter and WebSecurityConfigurerAdapter and config all stuff by yourself, but I think it's lose power of spring autoconfig.
Why I need config everything if I just need customize some config?

Spring Boot MongoDB connection bean

I have situation like this.
I'm using Spring boot 1.3.2, and I have installed MongoDB on my pc
I have added dependency
org.springframework.boot:spring-boot-starter-data-mongodb
And when I run my web application, database connection automatically start working.
I didn't configure a thing.
Now I want to connect spring security like this:
#Override
protected void configure(AuthenticationManagerBuilder auth)
                                                   throws Exception {
  auth
    .jdbcAuthentication()
      .dataSource(dataSource);
}
My question is what is default bean name for Spring Boot DataSource and can I override it?
If you're planning to use Mongodb as your user details storage, i.e. username, password, etc. , then you can't use the jdbcAuthentication(). Instead, You could use a UserDetailsService in order to achieve the same:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private MongoTemplate template;
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService((String username) -> {
User user = template.findOne(Query.query(Criteria.where("username").is(username)), User.class, "users");
if (user == null) throw new UsernameNotFoundException("Invalid User");
return new UserDetails(...);
});
}
}
In the prceeding sample, i supposed that you have a users collection with a username field. If exactly one user exists for the given username, you should return an implementation of UserDetails corresponding to that user. Otherwise, you should throw a UsernameNotFoundException.
You also have other options for handling user authentication but jdbcAuthentication() is off the table, since you're using a NoSQL datastore for storing user details and JDBC is an abstraction for handling all the talkings with Relational databases.

Spring Security: Authenticate against multiple LDAP servers & DAO-based authentication

I'm working on a Springboot application which has a requirement to support authentication locally (through a DAO-based provider) and through multiple LDAP servers (administratively configured, stored in the database).
With a single LDAP provider my configure method looks like:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
auth.ldapAuthentication()
.userSearchBase(userSearchBase)
.groupSearchBase(groupSearchBase)
.userSearchFilter(userSearchFilter)
.userDetailsContextMapper(new DaoUserDetailsContextMapper())
.contextSource().url(url+"/"+base)
.managerPassword(managerPassword)
.managerDn(managerDn);
}
Through other similar posts it appears this could be accomplished through creating multiple LDAP providers, and Spring security will cycle through each one until a successful login is found. I have the associated LDAP configuration record associated as a foreign key on the User table.
Is there a more efficient way to try the specific LDAP endpoint associated with the user, or is it best to let Spring iterate through the available providers?
Thanks for any input!
after long search in my way, I found something interesting about how Spring Security authentification works (there is a video : https://youtu.be/caCJAJC41Rk?t=781)
After that, you can use the system that Spring has implemented which is to override the supports(class<?> authenticationClass) method. This method works as "Can you, AuthenticationProvider, manage this kind of AuthenticationClass ?" if true, the provider will try to authenticate the user, if not, never execute any tasks.
In that way, you can implement your own CustomAuthentificationProvider which implements the AuthenticationProvider interface.
public class LocalUserAuthenticationProvider implements AuthenticationProvider {
private final UserRepository;
public LocalUserAuthenticationProvider(UserRepository userRepository){
this.userRepository = userRepository;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
/*my jobs*/
throws new AuthenticationException("Cannot authenticate");
}
#Override
public boolean supports(Class<?> aClass) {
return MyUsernameAuthenticationToken.class.isAssignableFrom(aClass);
}
}
with your own AuthenticationToken
public class StringUsernameAuthenticationToken extends UsernamePasswordAuthenticationToken {
public StringUsernameAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public StringUsernameAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
Actually I don't find any solutions with the LDAP authentication in the AuthenticationManagerBuilder implemented by Spring Security (ref : https://spring.io/guides/gs/authenticating-ldap/)
Hope this could help people

Resources