天天看点

若依框架对接LDAP

这里直接使用spring ldap实现认证。

背景在若依框架上对接LDAP

代码

引入依赖

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.unboundid</groupId>
            <artifactId>unboundid-ldapsdk</artifactId>
            <scope>test</scope>
        </dependency>
           

配置参数

spring:  
  ldap:
    urls: ldap://192.168.10.130
    base: dc=example,dc=com
    username: cn=Manager,dc=example,dc=com
    password: your secret
           

重写LdapTemplate

@Configuration
public class LdapConfiguration {
    @Value("${spring.ldap.urls}")
    private String urls;
    @Value("${spring.ldap.base}")
    private String base;
    @Value("${spring.ldap.username}")
    private String username;
    @Value("${spring.ldap.password}")
    private String password;
    
    private LdapTemplate ldapTemplate;
    
    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap<>();
        // 处理乱码
        config.put("java.naming.ldap.attributes.binary", "objectGUID");
        
        contextSource.setUrl(urls);
        contextSource.setBase(base);
        contextSource.setUserDn(username);
        contextSource.setPassword(password);
        // 不使用已经创建好连接
        //  如果使用已经创建连接,去验证,总是失败的
        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        // 验证必填参数都设置好
        contextSource.afterPropertiesSet();
        return contextSource;
    }
    
    @Bean
    public LdapTemplate ldapTemplate() {
        if (ldapTemplate == null) {
            ldapTemplate = new LdapTemplate(contextSource());
        }
        return ldapTemplate;
    }
           

LDAP person对象

根据实际LDAP服务的person名和具体属性

@Data
@Entry(base = "ou=People ", objectClasses = "inetOrgPerson")
public class LdapPerson {
    @Id
    @JsonIgnore
    private Name id;
    
    @DnAttribute(value = "uid")
    private String uid;
    
    @Attribute(name = "cn")
    private String cn;
    
    @Attribute(name = "sn")
    private String sn;
    
    @Attribute(name = "mobile")
    private String mobile;

    @Attribute(name = "mail")
    private String mail;
    
    @Attribute(name = "businessCategory")
    private String businessCategory;
    
    @Attribute(name = "departmentNumber")
    private String departmentNumber;
}
           

若依登入服务适配LDAP

在文件SysLoginService.java中(当前代码是2022.12月)

1.新增ldapValidate()方法(ldap认证,成功则将该账号自动注册)

2.在认证失败报错有"不存在",则调用ldapValidate()方法,再重新认证一次

@Component
public class SysLoginService
{
    private static final Logger logger = LoggerFactory.getLogger(SysLoginService.class);
    
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;
    
    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;

    @Resource
    private LdapTemplate ldapTemplate;
    
    @Resource
    private SysDeptMapper sysDeptMapper;
    
    @Resource
    private SysUserRoleMapper sysUserRoleMapper;

    /**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (captchaEnabled)
        {
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else if (e.getMessage().contains("不存在"))
            {
                // ldap认证,成功后注册
                ldapValidate(username, password);
                // 再认证一次
                logger.info("用户:{},LDAP认证成功,重新登入!", username);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
                AuthenticationContextHolder.setContext(authenticationToken);
                authentication = authenticationManager.authenticate(authenticationToken);
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }
    
    private void ldapValidate(String username, String password) {
        // 当用户不存在,尝试ldap去验证
        logger.info("用户:{},尝试LDAP认证", username);
        try
        {
            LdapQuery query = LdapQueryBuilder.query().where("uid").is(username);
            ldapTemplate.authenticate(query, password);
            LdapPerson person = ldapTemplate.findOne(query, LdapPerson.class);
            // 注册
            SysUser sysUser = new SysUser();
            SysDept dept = new SysDept();
            dept.setDeptName(person.getDepartmentNumber());
            List<SysDept> depts = sysDeptMapper.selectDeptList(dept);
            if (CollectionUtils.isEmpty(depts))
            {
                logger.error(dept.getDeptName() + "不在部门表中,请新增");
                throw new ServiceException(dept.getDeptName() + "不在部门表中,请新增");
            }

            // ldap信息无性别,无法判断. 默认全部设置男
            sysUser.setDeptId(depts.get(0).getDeptId());
            sysUser.setUserName(username);
            sysUser.setNickName(person.getSn());
            sysUser.setPassword(SecurityUtils.encryptPassword(password));
            sysUser.setEmail(person.getMail());
            sysUser.setPhonenumber(person.getMobile());
            sysUser.setCreateBy("ldap");
            // 普通岗位
            sysUser.setPostIds(new Long[]{2L});
            // 普通角色
            sysUser.setRoleIds(new Long[]{2L});
            if (userService.insertUser(sysUser) == 0)
            {
                throw new ServiceException("注册失败,请联系系统管理人员");
            }
            logger.info("用户:{},注册成功!", username);
        }
        catch (EmptyResultDataAccessException e1)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("LDAP不存在账号:" + username)));
            throw new ServiceException(e1.getMessage());
        }
        catch (AuthenticationException e2)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message(username + "账号在LDAP验证失败")));
            throw new ServiceException(e2.getMessage());
        }
    }

    /**
     * 校验验证码
     * 
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid)
    {
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}

           

增加一些sql

需要根据实际添加部门,否则新增用户没有部门id。

后续实际配置部门,权限等

insert into sys_dept values(500,  0,   '0',          'example',   0, '达网', '15888888888', '[email protected]', '0', '0', 'admin', sysdate(), '', null);insert into sys_dept values(503,  500, '0,500',  '研发部门',   1, '若依', '15888888888', '[email protected]', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(504,  500, '0,500',  '高级产品经理',   2, '若依', '15888888888', '[email protected]', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(505,  500, '0,500',  '产品经理',   3, '若依', '15888888888', '[email protected]', '0', '0', 'admin', sysdate(), '', null);

           

验证

当账号不存在自动注册

第一次登入:LDAP去认证,认证成功后,注册用户,再登入

第二次登入:直接登入成功

若依框架对接LDAP

再次请求:

若依框架对接LDAP

若依也可以打开(权限只是设置普通权限)

若依框架对接LDAP

参考

spring ldap:Spring LDAP Reference