autorenew

Implementing User Authentication with LDAP in Spring Boot

1. Add spring-boot-starter-data-ldap Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>

2. Configuration File

spring:
  ldap:
    urls: ldap://xx.xx.xx.xx:389
    base: OU=xxx,DC=xxx,DC=com
    username: xxx@xx.com
    password: xxx

Fill in the URL, baseDN, username, and password information in the configuration file

3. Create User Object Class

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;

import javax.naming.Name;

/**
 * LDAP protocol person class
 */
@Data
@Entry(objectClasses = "person")
public class Person {

    /**
     * Unique identifier
     */
    @Id
    @JSONField(serialize = false)
    private Name distinguishedName;

    /**
     * Login account
     */
    @Attribute(name = "sAMAccountName")
    private String loginName;

    /**
     * User name
     */
    @Attribute(name = "cn")
    private String name;

    /**
     * Permission code
     */
    @Attribute(name = "userAccountControl")
    private Integer userAccountControl;

    /**
     * Is deleted
     */
    private Boolean isDelete;
}

4. User Login

4.1 Example Code

import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.EqualsFilter;

private final LdapTemplate ldapTemplate;
private static final String LDAP_USERNAME_ATTR = "sAMAccountName";

public void login(String userName, String password) {
    EqualsFilter filter = new EqualsFilter(LDAP_USERNAME_ATTR, userName);
    boolean result = ldapTemplate.authenticate("", filter.toString(), password);
}

The basic logic is to call the ldapTemplate’s authenticate method for authentication.

4.2 Complete Code

package com.cowave.meter.admin.user.service.impl;

import cn.hutool.core.util.IdUtil;
import com.cowave.commons.framework.access.Access;
import com.cowave.commons.framework.filter.security.AccessToken;
import com.cowave.commons.framework.filter.security.TokenService;
import com.cowave.commons.framework.support.mybatis.page.PageDO;
import com.cowave.commons.framework.util.Asserts;
import com.cowave.commons.framework.util.AssertsException;
import com.cowave.meter.admin.user.dao.SysDeptDao;
import com.cowave.meter.admin.user.dao.SysRoleDao;
import com.cowave.meter.admin.user.dao.SysUserDao;
import com.cowave.meter.admin.user.pojo.Person;
import com.cowave.meter.admin.user.pojo.SysDept;
import com.cowave.meter.admin.user.pojo.SysRole;
import com.cowave.meter.admin.user.pojo.SysUser;
import com.cowave.meter.admin.user.service.LdapService;
import com.cowave.meter.admin.user.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class LdapServiceImpl implements LdapService {
    private final LdapTemplate ldapTemplate;
    private final TokenService tokenService;
    private final SysUserDao sysUserDao;
    private final SysUserService userService;
    private final SysDeptDao sysDeptDao;
    private final SysRoleDao sysRoleDao;
    private final BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
    private static final String LDAP_USERNAME_ATTR = "sAMAccountName";

    /**
     * Domain account login
     *
     * @param userName
     * @param password
     * @return
     */
    @Override
    public AccessToken domainLogin(String userName, String password) {
        EqualsFilter filter = new EqualsFilter(LDAP_USERNAME_ATTR, userName);
        boolean authenticate = ldapTemplate.authenticate("", filter.toString(), password);
        if (!authenticate) {
            throw new AssertsException("auth.failed");
        }
        Boolean domainUserAccountExist = sysUserDao.domainUserAccountExist(userName);
        if (Boolean.FALSE.equals(domainUserAccountExist)) {
            LdapQueryBuilder ldapQuery = LdapQueryBuilder.query();
            ldapQuery.filter(filter);
            Person person = ldapTemplate.findOne(ldapQuery, Person.class);
            if (ObjectUtils.isEmpty(person)) {
                throw new AssertsException("user.notexist");
            }

            SysUser sysUser = new SysUser();
            sysUser.setUserAccount(userName);
            Asserts.isFalse(userService.userAccountExist(sysUser), "Account name already exists");
            sysUser.setUserName(person.getName());
            sysUser.setUserPasswd(bcryptPasswordEncoder.encode(password));
            sysUser.setDomainUserAccount(userName);
            // Set department
            SysDept sysDept = sysDeptDao.queryRootDept();
            if (!ObjectUtils.isEmpty(sysDept)) {
                sysUser.setDeptId(sysDept.getDeptId());
            }
            // Set role
            PageDO<SysRole> sysRolePageDO = sysRoleDao.queryPage(null, "2", 1, 1);
            if (sysRolePageDO.getPages() > 0) {
                SysRole sysRole = sysRolePageDO.getList().get(0);
                sysUser.setRoleId(sysRole.getRoleId());
            }
            userService.saveOrUpdate(sysUser);
        }
        SysUser sysUser = sysUserDao.getByDomainUserAccount(userName);
        String userId = sysUser.getUserId();
        AccessToken accessToken = new AccessToken();
        accessToken.setType(AccessToken.TYPE_USER);

        accessToken.setUserCode(userId);
        accessToken.setUsername(sysUser.getUserAccount());
        accessToken.setUserNick(sysUser.getUserName());
        accessToken.setDeptCode(sysUser.getDeptId());
        accessToken.setRoles(List.of(sysUser.getRoleId()));

        accessToken.setLoginIp(Access.ip());
        accessToken.setLoginTime(Access.time());
        accessToken.setAccessIp(Access.ip());
        accessToken.setAccessTime(Access.time());
        accessToken.setId(IdUtil.fastSimpleUUID());

        String token = tokenService.newToken(accessToken);
        accessToken.setToken(token);
        return accessToken;
    }
}

4.3 Check if User is Disabled

/**
* Determine whether the user is disabled based on AD domain's userAccountControl attribute
*
* @param userAccContr
* @return
*/
private Boolean getUserDelete(int userAccContr) {
    //TRUSTED_TO_AUTH_FOR_DELEGATION - Allow this account to be delegated
    if (userAccContr >= 16777216) {
        userAccContr = userAccContr - 16777216;
    }
    //PASSWORD_EXPIRED - (Windows 2000/Windows Server 2003) User's password has expired
    if (userAccContr >= 8388608) {
        userAccContr = userAccContr - 8388608;
    }
    //DONT_REQ_PREAUTH
    if (userAccContr >= 4194304) {
        userAccContr = userAccContr - 4194304;
    }
    //USE_DES_KEY_ONLY - (Windows 2000/Windows Server 2003) Restrict this user to use only Data Encryption Standard (DES) encryption type keys
    if (userAccContr >= 2097152) {
        userAccContr = userAccContr - 2097152;
    }
    //NOT_DELEGATED - When this flag is set, the user's security context will not be delegated to the service even if the service account is trusted for Kerberos delegation
    if (userAccContr >= 1048576) {
        userAccContr = userAccContr - 1048576;
    }
    //TRUSTED_FOR_DELEGATION - When this flag is set, the service account (user or computer account) running the service is trusted for Kerberos delegation.
    // Any such service can impersonate the client requesting the service. To allow a service for Kerberos delegation, this flag must be set on the service account's userAccountControl attribute
    if (userAccContr >= 524288) {
        userAccContr = userAccContr - 524288;
    }
    //SMARTCARD_REQUIRED - When this flag is set, the user will be forced to use a smart card to log in
    if (userAccContr >= 262144) {
        userAccContr = userAccContr - 262144;
    }
    //MNS_LOGON_ACCOUNT - This is an MNS logon account
    if (userAccContr >= 131072) {
        userAccContr = userAccContr - 131072;
    }
    //DONT_EXPIRE_PASSWORD - Password never expires
    if (userAccContr >= 65536) {
        userAccContr = userAccContr - 65536;
    }
    //MNS_LOGON_ACCOUNT - This is an MNS logon account
    if (userAccContr >= 2097152) {
        userAccContr = userAccContr - 2097152;
    }
    //SERVER_TRUST_ACCOUNT - This is a computer account for a domain controller that belongs to this domain
    if (userAccContr >= 8192) {
        userAccContr = userAccContr - 8192;
    }
    //WORKSTATION_TRUST_ACCOUNT - This is a computer account for a computer running Microsoft Windows NT 4.0 Workstation, Microsoft Windows NT 4.0 Server,
    // Microsoft Windows 2000 Professional, or Windows 2000 Server that belongs to this domain
    if (userAccContr >= 4096) {
        userAccContr = userAccContr - 4096;
    }
    //INTERDOMAIN_TRUST_ACCOUNT - For system domains that trust other domains, this attribute allows accounts that trust this system domain
    if (userAccContr >= 2048) {
        userAccContr = userAccContr - 2048;
    }
    //NORMAL_ACCOUNT - This is the default account type representing a typical user
    if (userAccContr >= 512) {
        userAccContr = userAccContr - 512;
    }
    //TEMP_DUPLICATE_ACCOUNT - This account belongs to a user whose primary account is in another domain. This account provides the user with access to this domain,
    // but not access to any domains that trust this domain. Sometimes this account is called a "local user account"
    if (userAccContr >= 256) {
        userAccContr = userAccContr - 256;
    }
    //ENCRYPTED_TEXT_PASSWORD_ALLOWED - User can send encrypted passwords
    if (userAccContr >= 128) {
        userAccContr = userAccContr - 128;
    }
    //PASSWD_CANT_CHANGE - User cannot change password. This flag can be read but cannot be set directly
    if (userAccContr >= 64) {
        userAccContr = userAccContr - 64;
    }
    //PASSWD_NOTREQD - Password not required
    if (userAccContr >= 32) {
        userAccContr = userAccContr - 32;
    }
    //LOCKOUT
    if (userAccContr >= 16) {
        userAccContr = userAccContr - 16;
    }
    //HOMEDIR_REQUIRED - Home folder required
    if (userAccContr >= 8) {
        userAccContr = userAccContr - 8;
    }
    if (userAccContr >= 2) {
        return true;
    }
    return false;
}