如果你曾经在生产级应用程序中实现过登录功能,你一定听说过 LDAP 身份验证。
LDAP 可用于任何类型的分层目录信息,LDAP 最流行的用途是存储组织数据。
举个例子,比如说你们公司一个典型的组织结构,那么一般都有董事、经理、监事等职位。这些呢就是 LDAP 机制最适合实现的分层数据。通过这种方式,大多数组织使用它来维护组织信息,包括其凭证。
因此,我们接下来演示一下 Spring Security LDAP 身份验证示例。
LDAP 代表轻量级目录访问协议。它是一种开放的、供应商中立的行业标准应用程序协议,用于通过网络访问和维护分布式目录信息服务。
比如上面那个例子,LDAP 可用来存储组织的用户信息。
当用户尝试登录应用程序时,它会检查 LDAP 以查看其是否是有效用户以及该用户是否具有所需的权限和有效凭据。
在 Spring Security 的上下文中,应用程序连接到 LDAP 服务器,以便根据该 LDAP 验证有效用户。
LDIF 是 LDAP 数据交换格式的缩写。
它是文件的标准纯文本数据交换格式,用于表示 LDAP 目录内容,文件的扩展名是“.ldif”。
LDIF 文件中有多个字段,详细了解一下:
1)dn:专有名称
“dn”代表唯一标识目录中条目的名称。
DN 是必填字段,如果 DN 中存在逗号,则必须使用反斜杠(\)对逗号进行转义。例如,dn: uid=sam, ou=people, o=example.com Bolivia\,S.A.
2)o:组织
“o”代表组织名称。例如:juejin.cn
3)dc:域分量
“dc”代表域的每个组成部分。例如 www.example.com 将写为 DC = www, DC = example, DC = com
4)ou:组织单位
“ou”代表用户所属的组织单位(或有时是用户组)。如果用户属于多个组,您可以通过提供多个条目来指定它。例如:OU = 员工,OU = 管理员。
5)cn:通用名
“cn”代表您正在查询的单个对象(人名、会议室、菜谱名称、职位等)。它是人们常用的。例如,cn:郭德纲。至少需要一个通用名称。
6)sn:姓氏
“sn”代表人的姓氏。例如,sn:郭。需要提供姓氏。
7)objectClass:对象类
“objectClass”指定与此条目一起使用的对象类。
它是必填字段。例如:objectClass:person,objectClass:organizationalPerson。
应包含此对象类规范,因为许多 LDAP 客户端在对个人或组织人员进行搜索操作期间需要它。
LDIF 由一个或多个以空行分隔的目录条目组成。每个 LDIF 条目都包含一个可选的条目 ID、一个必需的专有名称、一个或多个对象类以及多个属性定义。
LDIF 格式在 RFC 2849 LDAP 数据交换格式 (LDIF) 中定义。 Sun Java System Directory Server 符合此标准。
LDIF 中表示的目录项的基本形式如下:
dn: distinguished_name
objectClass: object_class
objectClass: object_class
...
attribute_type[;subtype]:attribute_value
attribute_type[;subtype]:attribute_value
...
我们必须提供 DN 和至少一个对象类定义。此外,我们必须包含为条目定义的对象类所需的任何属性。
所有其他属性和对象类都是可选的。我们可以按任何顺序指定对象类和属性。冒号后面的空格也是可选的。
例如,下面是 LDIF 文件中的有效条目,这里的“uid”字段代表该人的用户 ID。
dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword
假设我们有一个应用程序,我们已经使用 Spring 框架提供的 Spring Security API 实现了基本的安全性。现在我们必须实施 LDAP 身份验证以使其更加安全。
因此,我们需要开发一个基本的 Web 应用程序,然后实现 LDAP 身份验证概念来满足 Spring Security LDAP 身份验证示例的要求。
以下是详细步骤:
创建入门项目时,选择“Spring Web”、“Spring LDAP”、“嵌入式 LDAP 服务器”、“Spring security”和“Spring Boot DevTools”作为入门项目依赖项。
这里“Spring Boot Dev Tools”是可选添加的。我们使用 Spring Boot Dev Tools 来实现热加载,避免在测试应用程序时多次重新启动 tomcat 服务器。除此之外,我们还需要添加一个额外的依赖项“spring-security-ldap”。
以下是我们使用 Spring Boot 开发此 Spring Security LDAP 身份验证示例所使用的依赖项。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-ldapartifactId>
dependency>
<dependency>
<groupId>com.unboundidgroupId>
<artifactId>unboundid-ldapsdkartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-ldapartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
dependency>
该文件将具有强制的 .ldif 扩展名。在 src/main/resources 文件夹下创建一个名为“ldap-data.ldif”的文件并更新数据,如下所示。
我们从 Spring 官方文档网站获取此文件。它将作为本地目录工作,并具有兼容的数据以相应地与 LDAP 配合使用。
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
dn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets
dn: ou="quoted people",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"
dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople
dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: $2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36
dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Admin
sn: Admin
uid: admin
userPassword: $2a$10$ODZhWAyD0i8zruWQcMYx9ePkW2xXsqIljhh4K8spSOUCY897ERkwu
dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword
dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword
dn: cn=mouse, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassword
dn: cn=slash/guy,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: slash/guy
sn: Slash
uid: slashguy
userPassword: slashguyspassword
dn: cn=quote"guy,ou="quoted people",dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: quote"guy
sn: Quote
uid: quoteguy
userPassword: quoteguyspassword
dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassword
dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: uid=bob,ou=people,dc=springframework,dc=org
dn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: cn=mouse, jerry,ou=people,dc=springframework,dc=org
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: submanagers
ou: submanager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
在这里,我们突出显示了一个用户的数据,我们将使用该数据来测试已实施的示例。
在此文件中,我们将拥有嵌入式 LDAP 的一些属性,如下所示。
spring.ldap.embedded.port=8389
spring.ldap.embedded.ldif=classpath:ldap-data.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
下一步是创建一个 RestController 类。
当我们输入凭据以登录应用程序时,我们将看到一条成功登录消息。让我们创建一个简单的控制器 LoginController.java,如下所示。
@RestController
public class LoginController {
@GetMapping("/")
public String getLoginPage() {
return "登录成功!";
}
}
这是我们将保留 LDAP 身份验证的整个逻辑的配置类,如下所示。
@Configuration
public class LdapSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
}
}
⚠️ 注意:如果您使用 Spring Security 5.7.0-M2 或更高版本,不要使用 WebSecurityConfigurerAdapter。
为了测试应用程序,我们需要打开浏览器并点击 URL http://localhost:8080/。
点击 URL 后,将显示默认登录页面。此外,输入 ldap-data.ldif 文件中给出的任何凭据。
如果输入的凭据正确,您将看到使用 LDAP 身份验证成功登录的消息。例如,让我们观察 ldap-data.ldif 文件中的以下用户数据。
dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Admin
sn: Admin
uid: admin
userPassword: $2a$10$ODZhWAyD0i8zruWQcMYx9ePkW2xXsqIljhh4K8spSOUCY897ERkwu
这里,aid是用户名,userpassword是密码的加密值。密码的真实值为“admin@123”。
因此,我们需要提供 admin@123 作为密码。单击登录按钮后,您应该在屏幕上看到消息“登录成功!”。
我们可以使用下面的程序类来对任何密码进行编码。
public class PasswordEncoder {
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("admin@123"));
}
}
在本文中,我们涵盖了“使用 Spring Boot 的 Spring Security LDAP 身份验证示例”的所有理论和示例部分。
最后,您应该能够实现 Spring Security LDAP 身份验证。同样,您也可以根据自己的要求进一步扩展此示例。也尝试在您的项目中相应地实现它。