Advertisement
Guest User

Untitled

a guest
Feb 1st, 2017
200
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 13.94 KB | None | 0 0
  1. package it.telecom.portal.configuration;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.dao.IncorrectResultSizeDataAccessException;
  5. import org.springframework.ldap.core.DirContextOperations;
  6. import org.springframework.ldap.core.DistinguishedName;
  7. import org.springframework.ldap.support.LdapUtils;
  8. import org.springframework.security.authentication.AccountExpiredException;
  9. import org.springframework.security.authentication.BadCredentialsException;
  10. import org.springframework.security.authentication.CredentialsExpiredException;
  11. import org.springframework.security.authentication.DisabledException;
  12. import org.springframework.security.authentication.LockedException;
  13. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  14. import org.springframework.security.core.GrantedAuthority;
  15. import org.springframework.security.core.authority.AuthorityUtils;
  16. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  17. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  18. import org.springframework.security.ldap.SpringSecurityLdapTemplate;
  19. import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
  20. import it.telecom.portal.configuration.ActiveDirectoryAuthenticationException;
  21. import it.telecom.portal.dao.LdapDAO;
  22. import it.telecom.portal.model.LDAP_PARAM;
  23.  
  24. import org.springframework.util.Assert;
  25. import org.springframework.util.StringUtils;
  26.  
  27. import javax.naming.AuthenticationException;
  28. import javax.naming.Context;
  29. import javax.naming.NamingException;
  30. import javax.naming.OperationNotSupportedException;
  31. import javax.naming.directory.DirContext;
  32. import javax.naming.directory.SearchControls;
  33. import javax.naming.ldap.InitialLdapContext;
  34. import java.util.*;
  35. import java.util.regex.Matcher;
  36. import java.util.regex.Pattern;
  37.  
  38. /**
  39.  * Specialized LDAP authentication provider which uses Active Directory configuration conventions.
  40.  * <p>
  41.  * It will authenticate using the Active Directory <a href="http://msdn.microsoft.com/en-us/library/ms680857%28VS.85%29.aspx">{@code userPrincipalName}</a> (in the form
  42.  * {@code username@domain}). If the username does not already end with the domain name, the {@code userPrincipalName} will be built by appending the configured domain name to the
  43.  * username supplied in the authentication request. If no domain name is configured, it is assumed that the username will always contain the domain name.
  44.  * <p>
  45.  * The user authorities are obtained from the data contained in the {@code memberOf} attribute.
  46.  *
  47.  * <h3>Active Directory Sub-Error Codes</h3>
  48.  *
  49.  * When an authentication fails, resulting in a standard LDAP 49 error code, Active Directory also supplies its own sub-error codes within the error message. These will be used to
  50.  * provide additional log information on why an authentication has failed. Typical examples are
  51.  *
  52.  * <ul>
  53.  * <li>525 - user not found</li>
  54.  * <li>52e - invalid credentials</li>
  55.  * <li>530 - not permitted to logon at this time</li>
  56.  * <li>532 - password expired</li>
  57.  * <li>533 - account disabled</li>
  58.  * <li>701 - account expired</li>
  59.  * <li>773 - user must reset password</li>
  60.  * <li>775 - account locked</li>
  61.  * </ul>
  62.  *
  63.  * If you set the {@link #setConvertSubErrorCodesToExceptions(boolean) convertSubErrorCodesToExceptions} property to {@code true}, the codes will also be used to control the
  64.  * exception raised.
  65.  *
  66.  * @author Luke Taylor
  67.  * @author Rob Winch
  68.  * @since 3.1
  69.  */
  70. @SuppressWarnings("deprecation")
  71. public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLdapAuthenticationProvider {
  72.     private static final Pattern SUB_ERROR_CODE = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*");
  73.  
  74.     // Error codes
  75.     private static final int USERNAME_NOT_FOUND = 0x525;
  76.     private static final int INVALID_PASSWORD = 0x52e;
  77.     private static final int NOT_PERMITTED = 0x530;
  78.     private static final int PASSWORD_EXPIRED = 0x532;
  79.     private static final int ACCOUNT_DISABLED = 0x533;
  80.     private static final int ACCOUNT_EXPIRED = 0x701;
  81.     private static final int PASSWORD_NEEDS_RESET = 0x773;
  82.     private static final int ACCOUNT_LOCKED = 0x775;
  83.  
  84.     // sapsuppliers.local
  85.     private final String domain;
  86.  
  87.     // dc=sapsuppliers,dc=local
  88.     private final String rootDn;
  89.  
  90.     // ldap://10.74.27.151:389
  91.     private final String url;
  92.  
  93.     private boolean convertSubErrorCodesToExceptions;
  94.  
  95.     @Autowired
  96.     private LdapDAO ldapDao;
  97.  
  98.     // Only used to allow tests to substitute a mock LdapContext
  99.     ContextFactory contextFactory = new ContextFactory();
  100.  
  101.     /**
  102.      * @param domain
  103.      *            the domain for which authentication should take place
  104.      */
  105.     // public ActiveDirectoryLdapAuthenticationProvider(String domain) {
  106.     // this (domain, null);
  107.     // }
  108.  
  109.     /**
  110.      * @param domain
  111.      *            the domain name (may be null or empty)
  112.      * @param url
  113.      *            an LDAP url (or multiple URLs)
  114.      */
  115.     public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) {
  116.         Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty");
  117.         this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null;
  118.         // this.url = StringUtils.hasText(url) ? url : null;
  119.         this.url = url;
  120.  
  121.         // dc=sapsuppliers,dc=local
  122.         rootDn = this.domain == null ? null : rootDnFromDomain(this.domain);
  123.         logger.debug("rootDn: " + rootDn);
  124.     }
  125.  
  126.     @Override
  127.     protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) {
  128.         String originalUsername = auth.getName();
  129.         String username = auth.getName();
  130.         String password = (String) auth.getCredentials();
  131.         logger.debug("username: " + originalUsername + " - password: " + password);
  132.  
  133.         // get ldapSearchBase
  134.         List<LDAP_PARAM> lparam = this.ldapDao.getParameter();
  135.        
  136.         for (LDAP_PARAM lp : lparam) {
  137.             if (lp.getParametro().equalsIgnoreCase("ldap_base")) {
  138.                 username = "CN=" + username + "," + lp.getValore();
  139.                 break;
  140.             }
  141.         }
  142.        
  143.         logger.debug("username: " + username + " - password: " + password);
  144.  
  145.         DirContext ctx = bindAsUser(username, password);
  146.  
  147.         try {
  148.             return searchForUser(ctx, originalUsername);
  149.  
  150.         } catch (NamingException e) {
  151.             logger.error("Failed to locate directory entry for authenticated user: " + username, e);
  152.             throw badCredentials(e);
  153.         } finally {
  154.             LdapUtils.closeContext(ctx);
  155.         }
  156.     }
  157.  
  158.     /**
  159.      * Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's Active Directory entry.
  160.      */
  161.     @Override
  162.     protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
  163.         String[] groups = userData.getStringAttributes("memberOf");
  164.  
  165.         if (groups == null) {
  166.             logger.debug("No values for 'memberOf' attribute.");
  167.  
  168.             return AuthorityUtils.NO_AUTHORITIES;
  169.         }
  170.  
  171.         if (logger.isDebugEnabled()) {
  172.             logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
  173.         }
  174.  
  175.         ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(groups.length);
  176.  
  177.         for (String group : groups) {
  178.             authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
  179.         }
  180.  
  181.         return authorities;
  182.     }
  183.  
  184.     private DirContext bindAsUser(String username, String password) {
  185.         // TODO. add DNS lookup based on domain
  186.         final String bindUrl = url;
  187.  
  188.         Hashtable<String, String> env = new Hashtable<String, String>();
  189.         env.put(Context.SECURITY_AUTHENTICATION, "simple");
  190.         // String bindPrincipal = createBindPrincipal(username);
  191.         String bindPrincipal = username;
  192.         env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
  193.         env.put(Context.PROVIDER_URL, bindUrl);
  194.         env.put(Context.SECURITY_CREDENTIALS, password);
  195.         env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
  196.  
  197.         try {
  198.             logger.debug("Context generato correttamente");
  199.             return contextFactory.createContext(env);
  200.         } catch (NamingException e) {
  201.             if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) {
  202.                 handleBindException(bindPrincipal, e);
  203.                 throw badCredentials(e);
  204.             } else {
  205.                 throw LdapUtils.convertLdapException(e);
  206.             }
  207.         }
  208.     }
  209.  
  210.     void handleBindException(String bindPrincipal, NamingException exception) {
  211.         if (logger.isDebugEnabled()) {
  212.             logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
  213.         }
  214.  
  215.         int subErrorCode = parseSubErrorCode(exception.getMessage());
  216.  
  217.         if (subErrorCode > 0) {
  218.             logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode));
  219.  
  220.             if (convertSubErrorCodesToExceptions) {
  221.                 raiseExceptionForErrorCode(subErrorCode, exception);
  222.             }
  223.         } else {
  224.             logger.debug("Failed to locate AD-specific sub-error code in message");
  225.         }
  226.     }
  227.  
  228.     int parseSubErrorCode(String message) {
  229.         Matcher m = SUB_ERROR_CODE.matcher(message);
  230.  
  231.         if (m.matches()) {
  232.             return Integer.parseInt(m.group(1), 16);
  233.         }
  234.  
  235.         return -1;
  236.     }
  237.  
  238.     public void raiseExceptionForErrorCode(int code, NamingException exception) {
  239.         String hexString = Integer.toHexString(code);
  240.         Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception);
  241.         switch (code) {
  242.         case PASSWORD_EXPIRED:
  243.             throw new CredentialsExpiredException(messages.getMessage("LdapAuthenticationProvider.credentialsExpired", "User credentials have expired"), cause);
  244.         case ACCOUNT_DISABLED:
  245.             throw new DisabledException(messages.getMessage("LdapAuthenticationProvider.disabled", "User is disabled"), cause);
  246.         case ACCOUNT_EXPIRED:
  247.             throw new AccountExpiredException(messages.getMessage("LdapAuthenticationProvider.expired", "User account has expired"), cause);
  248.         case ACCOUNT_LOCKED:
  249.             throw new LockedException(messages.getMessage("LdapAuthenticationProvider.locked", "User account is locked"), cause);
  250.         default:
  251.             throw badCredentials(cause);
  252.         }
  253.     }
  254.  
  255.     String subCodeToLogMessage(int code) {
  256.         switch (code) {
  257.         case USERNAME_NOT_FOUND:
  258.             return "User was not found in directory";
  259.         case INVALID_PASSWORD:
  260.             return "Supplied password was invalid";
  261.         case NOT_PERMITTED:
  262.             return "User not permitted to logon at this time";
  263.         case PASSWORD_EXPIRED:
  264.             return "Password has expired";
  265.         case ACCOUNT_DISABLED:
  266.             return "Account is disabled";
  267.         case ACCOUNT_EXPIRED:
  268.             return "Account expired";
  269.         case PASSWORD_NEEDS_RESET:
  270.             return "User must reset password";
  271.         case ACCOUNT_LOCKED:
  272.             return "Account locked";
  273.         }
  274.  
  275.         return "Unknown (error code " + Integer.toHexString(code) + ")";
  276.     }
  277.  
  278.     private BadCredentialsException badCredentials() {
  279.         return new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials"));
  280.     }
  281.  
  282.     private BadCredentialsException badCredentials(Throwable cause) {
  283.         return (BadCredentialsException) badCredentials().initCause(cause);
  284.     }
  285.  
  286.     private DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException {
  287.         SearchControls searchCtls = new SearchControls();
  288.         searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
  289.  
  290. //      String searchFilter = "(&(objectClass=user)(cn={0}))";
  291.         String searchFilter = "(&(objectClass=user)(sAMAccountName=" + username + "))";
  292.         logger.debug("searchFilter: "+searchFilter);
  293.  
  294.         // final String bindPrincipal = createBindPrincipal(username);
  295.         final String bindPrincipal = username;
  296.  
  297.         //String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
  298.         String searchRoot = rootDn;
  299.        
  300.         try {
  301.             return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter, new Object[] { bindPrincipal });
  302.         } catch (IncorrectResultSizeDataAccessException incorrectResults) {
  303.             if (incorrectResults.getActualSize() == 0) {
  304.                 UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username + " not found in directory.");
  305.                 userNameNotFoundException.initCause(incorrectResults);
  306.                 throw badCredentials(userNameNotFoundException);
  307.             }
  308.             // Search should never return multiple results if properly configured, so just rethrow
  309.             throw incorrectResults;
  310.         }
  311.     }
  312.  
  313. //  private String searchRootFromPrincipal(String bindPrincipal) {
  314. //      int atChar = bindPrincipal.lastIndexOf('@');
  315. //
  316. //      if (atChar < 0) {
  317. //          logger.debug("User principal '" + bindPrincipal + "' does not contain the domain, and no domain has been configured");
  318. //          throw badCredentials();
  319. //      }
  320. //
  321. //      return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
  322. //  }
  323.  
  324.     private String rootDnFromDomain(String domain) {
  325.         String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
  326.         StringBuilder root = new StringBuilder();
  327.  
  328.         for (String token : tokens) {
  329.             if (root.length() > 0) {
  330.                 root.append(',');
  331.             }
  332.             root.append("dc=").append(token);
  333.         }
  334.  
  335.         return root.toString();
  336.     }
  337.  
  338.     // String createBindPrincipal(String username) {
  339.     // if (domain == null || username.toLowerCase().endsWith(domain)) {
  340.     // return username;
  341.     // }
  342.     //
  343.     // return username + "@" + domain;
  344.     // }
  345.  
  346.     /**
  347.      * By default, a failed authentication (LDAP error 49) will result in a {@code BadCredentialsException}.
  348.      * <p>
  349.      * 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
  350.      * {@link CredentialsExpiredException}, {@link DisabledException}, {@link AccountExpiredException} or {@link LockedException} will be thrown for the corresponding codes. All
  351.      * other codes will result in the default {@code BadCredentialsException}.
  352.      *
  353.      * @param convertSubErrorCodesToExceptions
  354.      *            {@code true} to raise an exception based on the AD error code.
  355.      */
  356.     public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToExceptions) {
  357.         this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions;
  358.     }
  359.  
  360.     static class ContextFactory {
  361.         DirContext createContext(Hashtable<?, ?> env) throws NamingException {
  362.             return new InitialLdapContext(env, null);
  363.         }
  364.     }
  365. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement