• Skip to main content
  • Skip to primary sidebar
  • Skip to footer
  • About
  • Life
  • Tech
  • Travel
  • Work
  • Questions
  • Contact

Welcome

.

How to authenticate with LDAP email or mobile instead of username?

April 10, 2020 by

Questions › How to authenticate with LDAP email or mobile instead of username?
0
Vote Up
Vote Down
Garmaine asked 3 years ago

How to authenticate with LDAP email or mobile instead of username?

In a Spring boot 1.5.9 application, I am using spring-security-ldap and I use the default LdapUserDetailsManager class to login.

It use the loadUserByUsername method by default.

I have two types of users:

  • Back-Office: should be able login with username (or at the worst case, email)
  • Customers: should be able to login with mobile, email

This is the content of org.springframework.security.ldap.userdetails.LdapUserDetailsManager:

public class LdapUserDetailsManager implements UserDetailsManager {
    private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class);

    /**
     * The strategy for mapping usernames to LDAP distinguished names. This will be used
     * when building DNs for creating new users etc.
     */
    LdapUsernameToDnMapper usernameMapper = new DefaultLdapUsernameToDnMapper("cn=users",
            "uid");

    /** The DN under which groups are stored */
    private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups");

    /** Password attribute name */
    private String passwordAttributeName = "userPassword";

    /** The attribute which corresponds to the role name of a group. */
    private String groupRoleAttributeName = "cn";
    /** The attribute which contains members of a group */
    private String groupMemberAttributeName = "uniquemember";

    private final String rolePrefix = "ROLE_";

    /** The pattern to be used for the user search. {0} is the user's DN */
    private String groupSearchFilter = "(uniquemember={0})";
    /**
     * The strategy used to create a UserDetails object from the LDAP context, username
     * and list of authorities. This should be set to match the required UserDetails
     * implementation.
     */
    private UserDetailsContextMapper userDetailsMapper = new InetOrgPersonContextMapper();

    private final LdapTemplate template;

    /** Default context mapper used to create a set of roles from a list of attributes */
    private AttributesMapper roleMapper = new AttributesMapper() {

        public Object mapFromAttributes(Attributes attributes) throws NamingException {
            Attribute roleAttr = attributes.get(groupRoleAttributeName);

            NamingEnumeration<?> ne = roleAttr.getAll();
            // assert ne.hasMore();
            Object group = ne.next();
            String role = group.toString();

            return new SimpleGrantedAuthority(rolePrefix + role.toUpperCase());
        }
    };

    private String[] attributesToRetrieve;

    public LdapUserDetailsManager(ContextSource contextSource) {
        template = new LdapTemplate(contextSource);
    }

    public UserDetails loadUserByUsername(String username) {
        DistinguishedName dn = usernameMapper.buildDn(username);
        List<GrantedAuthority> authorities = getUserAuthorities(dn, username);

        logger.debug("Loading user '" + username + "' with DN '" + dn + "'");

        DirContextAdapter userCtx = loadUserAsContext(dn, username);

        return userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
    }

    private DirContextAdapter loadUserAsContext(final DistinguishedName dn,
            final String username) {
        return (DirContextAdapter) template.executeReadOnly(new ContextExecutor() {
            public Object executeWithContext(DirContext ctx) throws NamingException {
                try {
                    Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
                    return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
                }
                catch (NameNotFoundException notFound) {
                    throw new UsernameNotFoundException(
                            "User " + username + " not found", notFound);
                }
            }
        });
    }

    /**
     * Changes the password for the current user. The username is obtained from the
     * security context.
     * <p>
     * If the old password is supplied, the update will be made by rebinding as the user,
     * thus modifying the password using the user's permissions. If
     * <code>oldPassword</code> is null, the update will be attempted using a standard
     * read/write context supplied by the context source.
     * </p>
     *
     * @param oldPassword the old password
     * @param newPassword the new value of the password.
     */
    public void changePassword(final String oldPassword, final String newPassword) {
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();
        Assert.notNull(
                authentication,
                "No authentication object found in security context. Can't change current user's password!");

        String username = authentication.getName();

        logger.debug("Changing password for user '" + username);

        final DistinguishedName dn = usernameMapper.buildDn(username);
        final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(
                DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName,
                        newPassword)) };

        if (oldPassword == null) {
            template.modifyAttributes(dn, passwordChange);
            return;
        }

        template.executeReadWrite(new ContextExecutor() {

            public Object executeWithContext(DirContext dirCtx) throws NamingException {
                LdapContext ctx = (LdapContext) dirCtx;
                ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
                ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
                        LdapUtils.getFullDn(dn, ctx).toString());
                ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
                // TODO: reconnect doesn't appear to actually change the credentials
                try {
                    ctx.reconnect(null);
                }
                catch (javax.naming.AuthenticationException e) {
                    throw new BadCredentialsException(
                            "Authentication for password change failed.");
                }

                ctx.modifyAttributes(dn, passwordChange);

                return null;
            }
        });
    }

    /**
     *
     * @param dn the distinguished name of the entry - may be either relative to the base
     * context or a complete DN including the name of the context (either is supported).
     * @param username the user whose roles are required.
     * @return the granted authorities returned by the group search
     */
    @SuppressWarnings("unchecked")
    List<GrantedAuthority> getUserAuthorities(final DistinguishedName dn,
            final String username) {
        SearchExecutor se = new SearchExecutor() {
            public NamingEnumeration<SearchResult> executeSearch(DirContext ctx)
                    throws NamingException {
                DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
                SearchControls ctrls = new SearchControls();
                ctrls.setReturningAttributes(new String[] { groupRoleAttributeName });

                return ctx.search(groupSearchBase, groupSearchFilter, new String[] {
                        fullDn.toUrl(), username }, ctrls);
            }
        };

        AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(
                roleMapper);

        template.search(se, roleCollector);
        return roleCollector.getList();
    }

    public void createUser(UserDetails user) {
        DirContextAdapter ctx = new DirContextAdapter();
        copyToContext(user, ctx);
        DistinguishedName dn = usernameMapper.buildDn(user.getUsername());

        logger.debug("Creating new user '" + user.getUsername() + "' with DN '" + dn
                + "'");

        template.bind(dn, ctx, null);

        // Check for any existing authorities which might be set for this DN and remove
        // them
        List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());

        if (authorities.size() > 0) {
            removeAuthorities(dn, authorities);
        }

        addAuthorities(dn, user.getAuthorities());
    }

    public void updateUser(UserDetails user) {
        DistinguishedName dn = usernameMapper.buildDn(user.getUsername());

        logger.debug("Updating user '" + user.getUsername() + "' with DN '" + dn + "'");

        List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());

        DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
        ctx.setUpdateMode(true);
        copyToContext(user, ctx);

        // Remove the objectclass attribute from the list of mods (if present).
        List<ModificationItem> mods = new LinkedList<ModificationItem>(Arrays.asList(ctx
                .getModificationItems()));
        ListIterator<ModificationItem> modIt = mods.listIterator();

        while (modIt.hasNext()) {
            ModificationItem mod = (ModificationItem) modIt.next();
            Attribute a = mod.getAttribute();
            if ("objectclass".equalsIgnoreCase(a.getID())) {
                modIt.remove();
            }
        }

        template.modifyAttributes(dn, mods.toArray(new ModificationItem[mods.size()]));

        // template.rebind(dn, ctx, null);
        // Remove the old authorities and replace them with the new one
        removeAuthorities(dn, authorities);
        addAuthorities(dn, user.getAuthorities());
    }

    public void deleteUser(String username) {
        DistinguishedName dn = usernameMapper.buildDn(username);
        removeAuthorities(dn, getUserAuthorities(dn, username));
        template.unbind(dn);
    }

    public boolean userExists(String username) {
        DistinguishedName dn = usernameMapper.buildDn(username);

        try {
            Object obj = template.lookup(dn);
            if (obj instanceof Context) {
                LdapUtils.closeContext((Context) obj);
            }
            return true;
        }
        catch (org.springframework.ldap.NameNotFoundException e) {
            return false;
        }
    }

    /**
     * Creates a DN from a group name.
     *
     * @param group the name of the group
     * @return the DN of the corresponding group, including the groupSearchBase
     */
    protected DistinguishedName buildGroupDn(String group) {
        DistinguishedName dn = new DistinguishedName(groupSearchBase);
        dn.add(groupRoleAttributeName, group.toLowerCase());

        return dn;
    }

    protected void copyToContext(UserDetails user, DirContextAdapter ctx) {
        userDetailsMapper.mapUserToContext(user, ctx);
    }

    protected void addAuthorities(DistinguishedName userDn,
            Collection<? extends GrantedAuthority> authorities) {
        modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE);
    }

    protected void removeAuthorities(DistinguishedName userDn,
            Collection<? extends GrantedAuthority> authorities) {
        modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE);
    }

    private void modifyAuthorities(final DistinguishedName userDn,
            final Collection<? extends GrantedAuthority> authorities, final int modType) {
        template.executeReadWrite(new ContextExecutor() {
            public Object executeWithContext(DirContext ctx) throws NamingException {
                for (GrantedAuthority authority : authorities) {
                    String group = convertAuthorityToGroup(authority);
                    DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
                    ModificationItem addGroup = new ModificationItem(modType,
                            new BasicAttribute(groupMemberAttributeName, fullDn.toUrl()));

                    ctx.modifyAttributes(buildGroupDn(group),
                            new ModificationItem[] { addGroup });
                }
                return null;
            }
        });
    }

    private String convertAuthorityToGroup(GrantedAuthority authority) {
        String group = authority.getAuthority();

        if (group.startsWith(rolePrefix)) {
            group = group.substring(rolePrefix.length());
        }

        return group;
    }

    public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
        this.usernameMapper = usernameMapper;
    }

    public void setPasswordAttributeName(String passwordAttributeName) {
        this.passwordAttributeName = passwordAttributeName;
    }

    public void setGroupSearchBase(String groupSearchBase) {
        this.groupSearchBase = new DistinguishedName(groupSearchBase);
    }

    public void setGroupRoleAttributeName(String groupRoleAttributeName) {
        this.groupRoleAttributeName = groupRoleAttributeName;
    }

    public void setAttributesToRetrieve(String[] attributesToRetrieve) {
        Assert.notNull(attributesToRetrieve, "attributesToRetrieve cannot be null");
        this.attributesToRetrieve = attributesToRetrieve;
    }

    public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
        this.userDetailsMapper = userDetailsMapper;
    }

    /**
     * Sets the name of the multi-valued attribute which holds the DNs of users who are
     * members of a group.
     * <p>
     * Usually this will be <tt>uniquemember</tt> (the default value) or <tt>member</tt>.
     * </p>
     *
     * @param groupMemberAttributeName the name of the attribute used to store group
     * members.
     */
    public void setGroupMemberAttributeName(String groupMemberAttributeName) {
        Assert.hasText(groupMemberAttributeName, "groupMemberAttributeName should have text");
        this.groupMemberAttributeName = groupMemberAttributeName;
        this.groupSearchFilter = "(" + groupMemberAttributeName + "={0})";
    }

    public void setRoleMapper(AttributesMapper roleMapper) {
        this.roleMapper = roleMapper;
    }
}

It use an username (ldap uid) to perform login.

I want to be able to login using the mobile phone number or the email stored in LDAP.

Version

  • spring-security-ldap: 4.2.3.RELEASE

Question

  • Is there a tutorial or someone who have already done that before?
  • How can two login endpoints with differents criteria exists in Spring?
    • username
    • email or mobile

Thanks a lot for sharing your experience, time and helping

Are you looking for the answer?
Original Question and Possible Answers can be found on `http://stackoverflow.com`

Question Tags: java, ldap, spring, spring-security, spring-security-ldap

Please login or Register to submit your answer




Primary Sidebar

Tags

Advancements architecture beautiful life best building calling city commercial convenience employment Finances Cognitive decline Future gadgets Hidden Gems highway Home houses hydration Impact Innovations lamp lighting Mental health military tech Must-See New York City occupation Productivity recreation romance sepia shopping sippy cups smartphones social Technological breakthroughs technology toddlers Treasures turns Uncover Well-being Wonders Work Young onset dementia

Newsletter

Complete the form below, and we'll send you all the latest news.

Footer

Footer Funnies

Who knew that reading the footer could be such a hilarious adventure? As we navigate websites, books, and documents, we often stumble upon the unassuming space at the bottom, only to discover a treasure trove of amusement. In this side-splitting compilation, we present 100 jokes that celebrate the unsung hero of content – the footer. Get ready to chuckle, giggle, and maybe even snort as we dive into the world of footnotes, disclaimers, and hidden comedic gems. Brace yourself for a wild ride through the footer!

Recent

  • Unveiling the Enigma: Almost-Magical Lamp Lights Highway Turns
  • The Impact of Young Onset Dementia on Employment and Finances: Optimizing Post-Diagnostic Approaches
  • 11 Wonders of 2023 Technological Breakthrough – Unveiling the Future
  • Work from Home and Stay Mentally Sane – Achieve Productivity and Well-being
  • Hidden Gems of New York City – Uncover the Must-See Treasures!

Search

Tags

Advancements architecture beautiful life best building calling city commercial convenience employment Finances Cognitive decline Future gadgets Hidden Gems highway Home houses hydration Impact Innovations lamp lighting Mental health military tech Must-See New York City occupation Productivity recreation romance sepia shopping sippy cups smartphones social Technological breakthroughs technology toddlers Treasures turns Uncover Well-being Wonders Work Young onset dementia

Copyright © 2023