autorenew

springboot 基于 ldap 协议实现用户认证

一、引入 spring-boot-starter-data-ldap 依赖

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

二、配置文件

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

在配置文件里填入地址、baseDN、用户名和密码信息

三、创建用户对象类

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协议person类
 */
@Data
@Entry(objectClasses = "person")
public class Person {

    /**
     * 唯一标识
     */
    @Id
    @JSONField(serialize = false)
    private Name distinguishedName;

    /**
     * 登录账号
     */
    @Attribute(name = "sAMAccountName")
    private String loginName;

    /**
     * 用户姓名
     */
    @Attribute(name = "cn")
    private String name;

    /**
     * 权限码
     */
    @Attribute(name = "userAccountControl")
    private Integer userAccountControl;

    /**
     * 是否删除
     */
    private Boolean isDelete;
}

四、用户登录

4.1 示例代码

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);
}

基本逻辑就是调用 ldapTemplate 的 authenticate 方法来进行认证。

4.2 完整代码

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";

    /**
     * 域账号登录
     *
     * @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), "账户名已存在");
            sysUser.setUserName(person.getName());
            sysUser.setUserPasswd(bcryptPasswordEncoder.encode(password));
            sysUser.setDomainUserAccount(userName);
            // 设置部门
            SysDept sysDept = sysDeptDao.queryRootDept();
            if (!ObjectUtils.isEmpty(sysDept)) {
                sysUser.setDeptId(sysDept.getDeptId());
            }
            // 设置角色
            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 判断用户是否禁用

/**
* 根据AD域的userAccountControl属性判断用户是否禁用
*
* @param userAccContr
* @return
*/
private Boolean getUserDelete(int userAccContr) {
    //TRUSTED_TO_AUTH_FOR_DELEGATION - 允许该帐户进行委派
    if (userAccContr >= 16777216) {
        userAccContr = userAccContr - 16777216;
    }
    //PASSWORD_EXPIRED - (Windows 2000/Windows Server 2003) 用户的密码已过期
    if (userAccContr >= 8388608) {
        userAccContr = userAccContr - 8388608;
    }
    //DONT_REQ_PREAUTH
    if (userAccContr >= 4194304) {
        userAccContr = userAccContr - 4194304;
    }
    //USE_DES_KEY_ONLY - (Windows 2000/Windows Server 2003) 将此用户限制为仅使用数据加密标准 (DES) 加密类型的密钥
    if (userAccContr >= 2097152) {
        userAccContr = userAccContr - 2097152;
    }
    //NOT_DELEGATED - 设置此标志后,即使将服务帐户设置为信任其进行 Kerberos 委派,也不会将用户的安全上下文委派给该服务
    if (userAccContr >= 1048576) {
        userAccContr = userAccContr - 1048576;
    }
    //TRUSTED_FOR_DELEGATION - 设置此标志后,将信任运行服务的服务帐户(用户或计算机帐户)进行 Kerberos 委派。
    // 任何此类服务都可模拟请求该服务的客户端。若要允许服务进行 Kerberos 委派,必须在服务帐户的 userAccountControl 属性上设置此标志
    if (userAccContr >= 524288) {
        userAccContr = userAccContr - 524288;
    }
    //SMARTCARD_REQUIRED - 设置此标志后,将强制用户使用智能卡登录
    if (userAccContr >= 262144) {
        userAccContr = userAccContr - 262144;
    }
    //MNS_LOGON_ACCOUNT - 这是 MNS 登录帐户
    if (userAccContr >= 131072) {
        userAccContr = userAccContr - 131072;
    }
    //DONT_EXPIRE_PASSWORD-密码永不过期
    if (userAccContr >= 65536) {
        userAccContr = userAccContr - 65536;
    }
    //MNS_LOGON_ACCOUNT - 这是 MNS 登录帐户
    if (userAccContr >= 2097152) {
        userAccContr = userAccContr - 2097152;
    }
    //SERVER_TRUST_ACCOUNT - 这是属于该域的域控制器的计算机帐户
    if (userAccContr >= 8192) {
        userAccContr = userAccContr - 8192;
    }
    //WORKSTATION_TRUST_ACCOUNT - 这是运行 Microsoft Windows NT 4.0 Workstation、Microsoft Windows NT 4.0 Server、
    // Microsoft Windows 2000 Professional 或 Windows 2000 Server 并且属于该域的计算机的计算机帐户
    if (userAccContr >= 4096) {
        userAccContr = userAccContr - 4096;
    }
    //INTERDOMAIN_TRUST_ACCOUNT - 对于信任其他域的系统域,此属性允许信任该系统域的帐户
    if (userAccContr >= 2048) {
        userAccContr = userAccContr - 2048;
    }
    //NORMAL_ACCOUNT - 这是表示典型用户的默认帐户类型
    if (userAccContr >= 512) {
        userAccContr = userAccContr - 512;
    }
    //TEMP_DUPLICATE_ACCOUNT - 此帐户属于其主帐户位于另一个域中的用户。此帐户为用户提供访问该域的权限,
    // 但不提供访问信任该域的任何域的权限。有时将这种帐户称为"本地用户帐户"
    if (userAccContr >= 256) {
        userAccContr = userAccContr - 256;
    }
    //ENCRYPTED_TEXT_PASSWORD_ALLOWED - 用户可以发送加密的密码
    if (userAccContr >= 128) {
        userAccContr = userAccContr - 128;
    }
    //PASSWD_CANT_CHANGE - 用户不能更改密码。可以读取此标志,但不能直接设置它
    if (userAccContr >= 64) {
        userAccContr = userAccContr - 64;
    }
    //PASSWD_NOTREQD - 不需要密码
    if (userAccContr >= 32) {
        userAccContr = userAccContr - 32;
    }
    //LOCKOUT
    if (userAccContr >= 16) {
        userAccContr = userAccContr - 16;
    }
    //HOMEDIR_REQUIRED - 需要主文件夹
    if (userAccContr >= 8) {
        userAccContr = userAccContr - 8;
    }
    if (userAccContr >= 2) {
        return true;
    }
    return false;
}