Hello Team ,
I am trying to do ldap authentication but after putting user credentials again same login login page is coming. I really do not have any clue to what to do next.
Kindly help. Many thanks!
Sharing the code.
package com.wk.gbs.myaccount.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages="com.wk.gbs.myaccount.application")
public class AuthenticationApplication {
public static void main(String[] args) {
SpringApplication.run(AuthenticationApplication.class, args);
}
}
---
package com.wk.gbs.myaccount.application;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AuthenticationApplication.class);
}
}
---
package com.wk.gbs.myaccount.application.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.wk.gbs.myaccount.application.security.CustomActiveDirectoryLdapAuthenticationProvider;
/**
*
* @author TyroMind
* WebSecurityConfig class is created to provide configuration
* extending WebSecurityConfigurerAdapter to allows customization to httpSecurity
* Annotation Configuration to declare this class is configuration class
* Annotation EnableWebSecurity enables required WebSecurity for configuration class
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* Annotation Autowire used to create and object of CustomActiveDirectoryAuthProvider in WebSecurityConfig class
* and
* connect with WebSecurityConfig
*/
@Autowired
public CustomActiveDirectoryLdapAuthenticationProvider customActiveDirectoryLdapAuthenticationProvider;
@bean
public CustomActiveDirectoryLdapAuthenticationProvider customActiveDirectoryLdapAuthenticationProvider() {
return new CustomActiveDirectoryLdapAuthenticationProvider();
}
/**
* mentioned method is to override
* .authorizeRequests()
.anyRequest().fullyAuthenticated() authorize all requests coming in it and it
* should redirect to a login page
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin().permitAll()
;
}
/**
* SecurityBuilder used to create an AuthenticationManager.
* Allows for easily building in memory authentication, LDAP authentication, JDBC based authentication,
* adding UserDetailsService, and adding AuthenticationProvider
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customActiveDirectoryLdapAuthenticationProvider);
}
}
---
package com.wk.gbs.myaccount.application.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WKMvcConfigurer implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("index").setViewName("index");
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
}
---
package com.wk.gbs.myaccount.application.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.InitialLdapContext;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
//import org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Custom class created to override API class ActiveDirectoryLdapAuthenticationProvider, because of the following jiras:
* https://jira.spring.io/browse/SEC-1915
* https://jira.spring.io/browse/SEC-1823
* Changes made to method "searchForUser".
* ============ Diptayan Datta: 03/27/2015 ============
*/
@Component
public class CustomActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
private static final Pattern SUB_ERROR_CODE = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*");
// Error codes
private static final int USERNAME_NOT_FOUND = 0x525;
private static final int INVALID_PASSWORD = 0x52e;
private static final int NOT_PERMITTED = 0x530;
private static final int PASSWORD_EXPIRED = 0x532;
private static final int ACCOUNT_DISABLED = 0x533;
private static final int ACCOUNT_EXPIRED = 0x701;
private static final int PASSWORD_NEEDS_RESET = 0x773;
private static final int ACCOUNT_LOCKED = 0x775;
private String domain;
@SuppressWarnings("unused")
private String rootDn;
private String url;
private boolean convertSubErrorCodesToExceptions;
// Only used to allow tests to substitute a mock LdapContext
ContextFactory contextFactory = new ContextFactory();
public void setContextFactory(ContextFactory contextFactory) {
this.contextFactory = contextFactory;
}
/**
*
* @Param domain
*/
public void setDomain(String domain){
this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
}
/**
*
* @Param url
*/
public void setUrl(String url){
this.url = url;
}
@Override
protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {
String userName = auth.getName().split("@")[0];
String[] userNameValidation = auth.getName().split("@");
for (int i = 0; i < userNameValidation.length - 1; i++) {
String subDomainName = auth.getName().split("@")[1];
if (subDomainName != null && !subDomainName.toLowerCase().equalsIgnoreCase("wolterskluwer.com")) {
throw badCredentials(auth.getName());
}
}
userName = userName.toLowerCase();
String password = (String)auth.getCredentials();
return performAuthentication(userName,password,auth);
}
public DirContextOperations performAuthentication(String userName, String password, UsernamePasswordAuthenticationToken auth) {
DirContext ctx = bindAsUser(userName, password);
try {
return searchForUser(ctx, userName);
} catch (NamingException e) {
logger.error("Failed to locate directory entry for authenticated user: " + userName, e);
throw badCredentials(e,auth.getName());
} finally {
LdapUtils.closeContext(ctx);
}
}
/**
* Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's
* Active Directory entry.
*/
@Override
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
return new ArrayList<GrantedAuthority>();
}
public DirContext bindAsUser(String userName, String password) {
final String bindUrl = url;
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
String bindPrincipal = createBindPrincipal(userName);
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
try {
return contextFactory.createContext(env);
} catch (NamingException e) {
if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) {
handleBindException(bindPrincipal, e);
throw badCredentials(e,userName);
} else {
throw LdapUtils.convertLdapException(e);
}
}
}
void handleBindException(String bindPrincipal, NamingException exception) {
int subErrorCode = parseSubErrorCode(exception.getMessage());
if (subErrorCode > 0) {
logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode));
if (convertSubErrorCodesToExceptions) {
raiseExceptionForErrorCode(subErrorCode, exception, bindPrincipal);
}
} else {
logger.debug("Failed to locate AD-specific sub-error code in message");
}
}
int parseSubErrorCode(String message) {
Matcher m = SUB_ERROR_CODE.matcher(message);
if (m.matches()) {
return Integer.parseInt(m.group(1), 16);
}
return -1;
}
void raiseExceptionForErrorCode(int code, NamingException exception,String userName) {
//String hexString = Integer.toHexString(code); Commented as part of secure Assist fix, Unused snippet
Throwable cause = new AuthenticationException();
//Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
switch (code) {
case PASSWORD_EXPIRED:
try {
throw new Exception();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
case ACCOUNT_DISABLED:
throw new DisabledException(messages.getMessage("LdapAuthenticationProvider.disabled",
"User is disabled"), cause);
case ACCOUNT_EXPIRED:
throw new AccountExpiredException(messages.getMessage("LdapAuthenticationProvider.expired",
"User account has expired"), cause);
case ACCOUNT_LOCKED:
throw new LockedException(messages.getMessage("LdapAuthenticationProvider.locked",
"User account is locked"), cause);
default:
throw badCredentials(cause,userName);
}
}
String subCodeToLogMessage(int code) {
switch (code) {
case USERNAME_NOT_FOUND:
return "User was not found in directory";
case INVALID_PASSWORD:
return "Supplied password was invalid";
case NOT_PERMITTED:
return "User not permitted to logon at this time";
case PASSWORD_EXPIRED:
return "Password has expired";
case ACCOUNT_DISABLED:
return "Account is disabled";
case ACCOUNT_EXPIRED:
return "Account expired";
case PASSWORD_NEEDS_RESET:
return "User must reset password";
case ACCOUNT_LOCKED:
return "Account locked";
}
//return "Unknown (error code " + Integer.toHexString(code) +")"; Commented as part of secure Assist fix
return "Unknown (error code " + code +")";
}
private BadCredentialsException badCredentials(String userName) {
String[] userInfo = userName.split("@");
StringBuilder userNameInfo = new StringBuilder(userInfo[0]);
userNameInfo.append("@wolterskluwer.com");
return new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
}
//Modifier changed from private to protected to work with com.productportal.productsearch.web.ActiveDirectoryUserValidation
//Diptayan - 9/17/2015
public BadCredentialsException badCredentials(Throwable cause,String userName) {
return (BadCredentialsException) badCredentials(userName).initCause(cause);
}
//Modifier changed from private to protected to work with com.productportal.productsearch.web.ActiveDirectoryUserValidation
//Diptayan - 9/17/2015
public DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException {
SearchControls searchCtls = new SearchControls();
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
//String searchFilter = "(&(objectClass=user)(SAMAccountName={0})";
String searchFilter = "(SAMAccountName={0})";
//final String bindPrincipal = createBindPrincipal(username);
final String bindPrincipal = username;
//String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
String searchRoot = "dc=na,dc=wkglobal,dc=com";
//TODO: Select specific return parameters from LDAP response (like "CN")
try {
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter,
new Object[]{bindPrincipal});
} catch (IncorrectResultSizeDataAccessException incorrectResults) {
if (incorrectResults.getActualSize() == 0) {
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username + " not found in directory.");
userNameNotFoundException.initCause(incorrectResults);
throw badCredentials(userNameNotFoundException,username);
}
// Search should never return multiple results if properly configured, so just rethrow
throw incorrectResults;
}
}
protected String searchRootFromPrincipal(String bindPrincipal) {
int atChar = bindPrincipal.lastIndexOf('@');
if (atChar < 0) {
logger.debug("User principal '" + bindPrincipal + "' does not contain the domain, and no domain has been configured");
throw badCredentials(bindPrincipal);
}
return rootDnFromDomain(bindPrincipal.substring(atChar+ 1, bindPrincipal.length()));
}
private String rootDnFromDomain(String domain) {
String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
StringBuilder root = new StringBuilder();
for (String token : tokens) {
if (root.length() > 0) {
root.append(',');
}
root.append("dc=").append(token);
}
return root.toString();
}
String createBindPrincipal(String username) {
if (domain == null || username.toLowerCase().endsWith(domain)) {
return username;
}
return username + "@" + domain;
}
/**
* By default, a failed authentication (LDAP error 49) will result in a {@code BadCredentialsException}.
* <p>
* If this property is set to {@code true}, the exception message from a failed bind attempt will be parsed
* for the AD-specific error code and a {@link CredentialsExpiredException}, {@link DisabledException},
* {@link AccountExpiredException} or {@link LockedException} will be thrown for the corresponding codes. All
* other codes will result in the default {@code BadCredentialsException}.
*
* @Param convertSubErrorCodesToExceptions {@code true} to raise an exception based on the AD error code.
*/
public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToExceptions) {
this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions;
}
static class ContextFactory {
DirContext createContext(Hashtable<?,?> env) throws NamingException {
return new InitialLdapContext(env, null);
}
}
}