引言
在企业环境中,轻量级目录访问协议(ldap)扮演着重要角色,作为集中式用户管理和身份验证的标准协议。ldap服务器存储组织结构化数据,包括用户、组织和权限信息。
spring ldap是spring家族的一个子项目,它简化了java应用与ldap服务器的交互过程。
一、spring ldap基础
spring ldap提供了一个抽象层,使开发者能够以spring风格的方式与ldap交互,避免直接处理底层jndi api的复杂性。
它遵循与spring jdbc相似的模板模式,通过ldaptemplate提供了简洁的接口来执行ldap操作。
要开始使用spring ldap,首先需要添加相关依赖。对于maven项目,可以在pom.xml中添加:
<dependency>
<groupid>org.springframework.ldap</groupid>
<artifactid>spring-ldap-core</artifactid>
<version>2.4.1</version>
</dependency>
<!-- 集成spring boot -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-ldap</artifactid>
</dependency>在spring boot项目中,配置ldap连接信息可以在application.properties或application.yml中完成:
# ldap服务器配置 spring.ldap.urls=ldap://ldap.example.com:389 spring.ldap.base=dc=example,dc=com spring.ldap.username=cn=admin,dc=example,dc=com spring.ldap.password=admin_password
二、ldaptemplate详解
ldaptemplate是spring ldap的核心类,它封装了ldap操作的复杂性,提供了一套简洁的api。
在spring boot环境中,ldaptemplate会被自动配置,可以直接注入使用:
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.ldap.core.ldaptemplate;
import org.springframework.stereotype.service;
@service
public class ldapservice {
private final ldaptemplate ldaptemplate;
@autowired
public ldapservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
// 使用ldaptemplate执行ldap操作
}如果不使用spring boot,则需要手动配置ldaptemplate:
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.ldap.core.ldaptemplate;
import org.springframework.ldap.core.support.ldapcontextsource;
@configuration
public class ldapconfig {
@bean
public ldapcontextsource contextsource() {
ldapcontextsource contextsource = new ldapcontextsource();
contextsource.seturl("ldap://ldap.example.com:389");
contextsource.setbase("dc=example,dc=com");
contextsource.setuserdn("cn=admin,dc=example,dc=com");
contextsource.setpassword("admin_password");
return contextsource;
}
@bean
public ldaptemplate ldaptemplate() {
return new ldaptemplate(contextsource());
}
}ldaptemplate提供了多种方法来执行ldap操作,包括搜索、绑定、修改和删除等。它还支持回调方法,允许开发者自定义结果处理逻辑。
三、ldap对象映射
spring ldap提供了对象-目录映射(odm)功能,类似于orm(对象-关系映射),可以将ldap条目映射到java对象。通过使用注解,可以轻松实现ldap条目与java类之间的转换:
import org.springframework.ldap.odm.annotations.*;
import javax.naming.name;
@entry(base = "ou=people", objectclasses = {"person", "inetorgperson"})
public class user {
@id
private name id;
@attribute(name = "cn")
private string commonname;
@attribute(name = "sn")
private string surname;
@attribute(name = "mail")
private string email;
@attribute(name = "telephonenumber")
private string phonenumber;
// getters and setters
public name getid() {
return id;
}
public void setid(name id) {
this.id = id;
}
public string getcommonname() {
return commonname;
}
public void setcommonname(string commonname) {
this.commonname = commonname;
}
// 其他getters和setters
}在上面的例子中,@entry注解定义了ldap条目的基本信息,@id注解标记了条目的唯一标识符,@attribute注解将java属性映射到ldap属性。
四、基本ldap操作
4.1 查询操作
使用ldaptemplate进行查询是最常见的操作。可以使用各种方法来执行搜索:
import org.springframework.ldap.filter.equalsfilter;
import org.springframework.ldap.filter.filter;
import org.springframework.ldap.core.ldaptemplate;
import org.springframework.ldap.core.attributesmapper;
import org.springframework.stereotype.service;
import javax.naming.directory.attributes;
import java.util.list;
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public list<string> getallusernames() {
return ldaptemplate.search(
"ou=people", // 搜索基础
"(objectclass=person)", // 搜索过滤器
(attributesmapper<string>) attrs -> (string) attrs.get("cn").get() // 属性映射
);
}
public list<user> finduserbyemail(string email) {
filter filter = new equalsfilter("mail", email);
return ldaptemplate.search(
"ou=people",
filter.encode(),
(attributesmapper<user>) attrs -> {
user user = new user();
user.setcommonname((string) attrs.get("cn").get());
user.setsurname((string) attrs.get("sn").get());
user.setemail((string) attrs.get("mail").get());
return user;
}
);
}
}使用odm功能,可以直接将搜索结果映射到java对象:
import org.springframework.data.ldap.repository.ldaprepository;
import org.springframework.ldap.core.ldaptemplate;
import org.springframework.ldap.query.ldapquery;
import org.springframework.ldap.query.ldapquerybuilder;
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public list<user> finduserbyemail(string email) {
ldapquery query = ldapquerybuilder.query()
.base("ou=people")
.where("objectclass").is("person")
.and("mail").is(email);
return ldaptemplate.find(query, user.class);
}
}4.2 添加操作
添加新条目可以通过直接创建对象然后使用ldaptemplate的create方法:
import org.springframework.ldap.core.dircontextadapter;
import org.springframework.ldap.core.ldaptemplate;
import org.springframework.ldap.support.ldapnamebuilder;
import javax.naming.name;
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void createuser(string username, string surname, string email) {
name dn = ldapnamebuilder.newinstance()
.add("ou", "people")
.add("cn", username)
.build();
dircontextadapter context = new dircontextadapter(dn);
context.setattributevalues("objectclass", new string[]{"top", "person", "inetorgperson"});
context.setattributevalue("cn", username);
context.setattributevalue("sn", surname);
context.setattributevalue("mail", email);
ldaptemplate.bind(context);
}
}使用odm功能,可以更简单地创建和保存对象:
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void createuser(string username, string surname, string email) {
user user = new user();
user.setid(ldapnamebuilder.newinstance()
.add("cn", username)
.build());
user.setcommonname(username);
user.setsurname(surname);
user.setemail(email);
ldaptemplate.create(user);
}
}4.3 修改操作
修改现有条目可以通过查找条目,修改属性,然后更新:
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void updateuseremail(string username, string newemail) {
name dn = ldapnamebuilder.newinstance()
.add("ou", "people")
.add("cn", username)
.build();
dircontextoperations context = ldaptemplate.lookupcontext(dn);
context.setattributevalue("mail", newemail);
ldaptemplate.modifyattributes(context);
}
}使用odm功能:
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void updateuseremail(string username, string newemail) {
ldapquery query = ldapquerybuilder.query()
.base("ou=people")
.where("cn").is(username);
user user = ldaptemplate.findone(query, user.class);
if (user != null) {
user.setemail(newemail);
ldaptemplate.update(user);
}
}
}4.4 删除操作
删除条目的操作比较简单:
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void deleteuser(string username) {
name dn = ldapnamebuilder.newinstance()
.add("ou", "people")
.add("cn", username)
.build();
ldaptemplate.unbind(dn);
}
}使用odm功能:
@service
public class userservice {
private final ldaptemplate ldaptemplate;
public userservice(ldaptemplate ldaptemplate) {
this.ldaptemplate = ldaptemplate;
}
public void deleteuser(string username) {
ldapquery query = ldapquerybuilder.query()
.base("ou=people")
.where("cn").is(username);
user user = ldaptemplate.findone(query, user.class);
if (user != null) {
ldaptemplate.delete(user);
}
}
}五、认证与授权
spring ldap可以与spring security集成,实现基于ldap的认证和授权:
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.security.config.annotation.authentication.builders.authenticationmanagerbuilder;
import org.springframework.security.config.annotation.web.builders.httpsecurity;
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;
import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter;
import org.springframework.security.crypto.password.nooppasswordencoder;
import org.springframework.security.crypto.password.passwordencoder;
@configuration
@enablewebsecurity
public class securityconfig extends websecurityconfigureradapter {
@override
protected void configure(authenticationmanagerbuilder auth) throws exception {
auth
.ldapauthentication()
.userdnpatterns("cn={0},ou=people")
.groupsearchbase("ou=groups")
.contextsource()
.url("ldap://ldap.example.com:389/dc=example,dc=com")
.and()
.passwordcompare()
.passwordattribute("userpassword");
}
@override
protected void configure(httpsecurity http) throws exception {
http
.authorizerequests()
.antmatchers("/admin/**").hasrole("admin")
.antmatchers("/user/**").hasrole("user")
.anyrequest().authenticated()
.and()
.formlogin();
}
@bean
public passwordencoder passwordencoder() {
// 注意:生产环境不应使用nooppasswordencoder
return nooppasswordencoder.getinstance();
}
}六、高级特性与最佳实践
spring ldap提供了一些高级特性,如分页查询、排序和连接池配置,这些对于处理大型目录服务尤为重要:
// 配置连接池
@bean
public ldapcontextsource contextsource() {
ldapcontextsource contextsource = new ldapcontextsource();
contextsource.seturl("ldap://ldap.example.com:389");
contextsource.setbase("dc=example,dc=com");
contextsource.setuserdn("cn=admin,dc=example,dc=com");
contextsource.setpassword("admin_password");
// 连接池配置
contextsource.setpooled(true);
return contextsource;
}
@bean
public poolingcontextsource poolingcontextsource(ldapcontextsource contextsource) {
defaulttlsdircontextauthenticationstrategy strategy = new defaulttlsdircontextauthenticationstrategy();
strategy.sethostnameverifier((hostname, session) -> true);
contextsource.setauthenticationstrategy(strategy);
poolconfig poolconfig = new poolconfig();
poolconfig.setminidle(5);
poolconfig.setmaxtotal(20);
poolconfig.setmaxidle(10);
poolingcontextsource poolingcontextsource = new poolingcontextsource();
poolingcontextsource.setcontextsource(contextsource);
poolingcontextsource.setpoolconfig(poolconfig);
return poolingcontextsource;
}
// 分页查询示例
public list<user> finduserspaged(int pagesize, int pagenumber) {
pagedresultsdircontextprocessor processor = new pagedresultsdircontextprocessor(pagesize);
ldapquery query = ldapquerybuilder.query()
.base("ou=people")
.where("objectclass").is("person");
// 执行第一页查询
list<user> users = new arraylist<>();
for (int i = 0; i < pagenumber; i++) {
users = ldaptemplate.search(query, new personattributesmapper(), processor);
// 如果没有更多结果或者已经到达请求的页码,则停止
if (!processor.hasmore() || i == pagenumber - 1) {
break;
}
// 设置cookie以获取下一页
processor.updatecookie();
}
return users;
}总结
spring ldap为开发者提供了一个强大且灵活的框架,简化了与ldap目录服务的交互。通过ldaptemplate,开发者可以轻松执行各种ldap操作,而无需深入了解底层jndi api的复杂性。对象-目录映射功能让ldap条目与java对象的转换变得简单直观,提高了代码的可读性和可维护性。与spring security的集成使得实现基于ldap的身份验证和授权变得轻而易举。在企业应用中,特别是需要集中式用户管理的场景下,spring ldap是一个理想的选择。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论