这里直接使用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', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);insert into sys_dept values(503, 500, '0,500', '研发部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(504, 500, '0,500', '高级产品经理', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
insert into sys_dept values(505, 500, '0,500', '产品经理', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', sysdate(), '', null);
验证
当账号不存在自动注册
第一次登入:LDAP去认证,认证成功后,注册用户,再登入
第二次登入:直接登入成功
再次请求:
若依也可以打开(权限只是设置普通权限)
参考
spring ldap:Spring LDAP Reference