官网上非常粗犷的提供了一篇关于springboot如何利用LDAP构建一个安全的应用的文档: https://spring.io/guides/gs/authenticating-ldap/
但是在实际尝试的时候,出现了非常多的疑惑点,包括:
- 什么是LDAP?
- 那四个新的依赖是干嘛的?
- 如何设置密码
- 等等
所以写下此篇文档前花了一些时间,去让自己明白,这,到底是什么以及怎么弄!
首先,我们来看看官网让我们装载的新的相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
我们就从这四个依赖入手,去明白这是怎么一回事~~~
1. 依赖:spring-boot-starter-security
我们之前的demo里面,创建的直接可以通过api获取数据的那些web应用,都是非常不安全的。
那么什么是安全的呢?
简单的来说,就是需要使用的时候,有安全验证,从各个访问的环节,尽可能的阻拦一些风险。
常用的方法就比如我们使用api的时候,需要带用户名密码,或者需要带登录后定时失效的token才可以访问等等。
这里有一篇很好的文章说明了我们要用的第一个依赖: spring-boot-starter-security
spring-boot-starter-security与应用安全
在当前项目中只要添加需要的 Controller 实现,一个添加了基本安全防护的 Web 应用就诞生了。spring-boot-starter-security 默认会提供一个基于 HTTP Basic 认证的安全防护策略,默认用户名为 user,访问密码则在当前 Web 应用启动的时候,打印到控制台,类似于:
2017-01-01 13:57:00.596 INFO 17966 --- [ost-startStop-1] b.a.s.Au-thenticationManagerConfiguration : Using default security password: 560ff91b-0ae7-492c-ad16-603e1adec54c
如果我们希望对 HTTP Basic 认证的用户名和密码进行定制,可以通过如下配置项进行:
security.user.name={个人希望设置的用户名}\
security.user.password={个人希望使用的访问密码}
也就是说, spring-boot-starter-security
提供了一个非常基础的安全防护,让你可以通过默认密码或者自定义的用户名密码,来访问当前网页应用。
2. 依赖:spring-ldap-core 和 spring-security-ldap
1. 初识LDAP
去搜索这两个依赖的时候,都不约而同的指向了LDAP这个概念,所以这一节就需要弄明白LDAP到底是个什么东西.
先看看官方说明:
LDAP,Lightweight Directory Access Protocol,轻量级目录访问协议
无疑,这里又出现了新的疑问,什么是目录访问协议,这些又是什么!
根据网上大量的各种博客和资料,最终的总结如下:
- LDAP是一种特殊的服务器,可以存储数据
- 数据的存储是目录形式的,或者可以理解为树状结构(一层套一层)
- 一般存储关于用户、用户认证信息、组、用户成员,通常用于用户认证与授权
我们看看上图,就可以发现,LDAP可以通过设置这些o/ou/cn这些标志位,来创建一个树状结构的用户数据,还能把用户所属的公司、组织、部门、同部门的用户等都存起来。
这样的话,就很容易界定用户的类别和用户的权限,也方便进行用户查询
2. LDAP的标志位说明:依赖:unboundid
上一节的图中,我们可以看到一些o/ou/cn/dn这些标志位,或者说是字段,那么大概是什么意思呢?
首先,我们需要引入unboundid这个maven依赖:
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
我们来看一下官方的解释:
UnboundID LDAP SDK提供了一套快速、强大、用户友好并且开源的Java API来与LDAP目录服务器交互。与其它基于Java的LDAP APIs相比,它具有更好的性能、更易于使用,功能更多。而且还是唯一个不断有活跃开发和增强的SDK。
简单的来说,这玩意就是提供了一个更为友好的LDAP的交互配置的sdk,也就是我们所理解的LDAP的标志位。通过配置这些东西,就可以简单的通过一个配置文件,建立一套LDAP的目录数据。
大致的总结如下:
标识位 | 英文全称 | 中文说明 |
---|---|---|
cn | common name | 通用名 |
ou | organization unit | 组织单位 |
o | organization | 组织名称 |
uid | userid | 对象id |
dc | domain component | dns中的每个元素,例如com.cn中com和cn都是一个dc |
dn | distiguished name | 可以看做dns,但组成dn的每个值都有自己的属性,如上例:dn为dc=com,dc=cn;同时,dn也可以表示某个目录,或者某个目录中的对象,如用户名等 |
那我们来理解一下官网例子中的配置信息:(很多,但是解读后有助于理解)
## 这是LDAP目录的第一层位置
## dns是:springframework.org
## 当前的dns的元素位置为springframework
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework
## 这是LDAP第二层位置,隶属于上面的目录之下,产生了一个组织名为groups
## dns为:groups.springframework.org
## 组织单位为:groups
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
## 这是LDAP第三层位置,是组织groups的下级组织:subgroups
## dns为:subgroups.groups.springframework.org
## 组织单位:subgroups
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups
## 这是一个和groups同级别的组织:people,至此,第一级目录有了两个子集组织:people和groups
## dns为:people.springframework.org
## 组织单位:people
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
## 这是一个和groups同级别的组织:space cadets,至此,第一级目录有了三个子集组织:space cadets、people和groups
## dns为:space cadets.springframework.org
## 组织单位:space cadets
dn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets
## 这是一个和groups同级别的组织:\"quoted people\",至此,第一级目录有了四个子集组织:\"quoted people\"、space cadets、people和groups
## dns为:\"quoted people\".springframework.org
## 组织单位:"quoted people"
dn: ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"
## 这是一个和groups同级别的组织:otherpeople,至此,第一级目录有了五个子集组织:otherpeople、\"quoted people\"、space cadets、people和groups
## dns为:otherpeople.springframework.org
## 组织单位:otherpeople
dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople
## 描述用户信息:隶属于people这个组织下,有自己的用户名和密码
## dns为:ben.people.springframework.org
## 通用名:Ben Alex
## userid:ben
## 密码:$2a$10$c6bSeWPhg06xB1lvmaWNNe4NROmZiSpYhlocU/98HNr2MhIOiSt36(加密字符串)
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
## 描述用户信息:隶属于people这个组织下,有自己的用户名和密码
## dns为:bob.people.springframework.org
## 通用名:Bob Hamilton
## userid:bob
## 密码:bobspassword
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
## 描述用户信息:隶属于otherpeople这个组织下,有自己的用户名和密码
## dns为:joe.otherpeople.springframework.org
## 通用名:Joe Smeth
## userid:joe
## 密码:joespassword
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
此外,还有几个同样的用户,我们就不赘述了:
jerry、slashguy、quoteguy、space cadet
最后,还列举了一些group中的用户权限
## 这是一个隶属于groups的组织:developers
## dns为:developers.groups.springframework.org
## 组织单位:developer
## 这里面有两个独立用户:ben和bob
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
## 这是一个隶属于groups的组织:managers
## dns为:managers.groups.springframework.org
## 组织单位:managers
## 这里面有两个独立用户:ben和jerry
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
## 这是一个隶属于subgroups的组织:submanagers
## dns为:submanagers.subgroups.groups.springframework.org
## 组织单位:managers
## 这里面有一个独立用户:ben
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的配置,我们可以创建自定义层级的各种组织架构,还可以添加各类用户,并为用户设置角色权限。可以说,这基本上实现了一个标准的用户角色权限与组织架构的基本功能。
3. LDAP工程实践
现在,我们开始往之前创建的RESTful应用中,添加LDAP的设置:
1. 添加依赖
先往pom.xml中添加maven依赖,并更新
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
</dependency>
2. 创建LDAP角色的配置文件: test-server.ldif
在resource中创建一个新的文件,并把角色权限和组织的配置信息放进去
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=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
3. 在 application.properties
中指定LDAP服务器使用上述配置文件
- 指定使用test-server.ldif文件内容
- 指定LDAP目录的根目录为springframework.org
- 指定LDAP服务器使用端口号为8388(可选)
# Spring-LDAP user authentication
spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8388
4. 创建使用LDAP服务的java配置文件
package shenling.example.springbootJDBC.config;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class WebSecurityConfig 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:8388/dc=springframework,dc=org") // 此处指定了LDAP服务器路径,端口号为我们自定义的8388
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder()) // 此处指定了可以使用encrypted的密码版本,和我们的用户信息配置有关系
.passwordAttribute("userPassword");
}
}
4. 运行结果
正常输入localhost后,会跳转到login页面:
输入用户名/密码:ben/benspassword,登录成功
5. 使用非encrypted的密码登录失败
我们发现,使用bob等以明文形式存储密码的用户进行登录,会出现登录失败的问题:
控制台提示说密码不是encrypted的
2021-12-14 11:01:54.879 WARN 57415 --- [nio-8080-exec-3] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
所以,我们注释掉了java文件中关于这个密码的这一行:
// .passwordEncoder(new BCryptPasswordEncoder())
即可登录成功
6. 延伸学习:单点登录
LDAP实际上也算是一种单点登录的方式,当然,我们在企业级应用中更为常见的应该是SSO或者OAuth这一类的配置,便于通过一个登录站点,可登录多个不同的外部服务。
那么,LDAP和OAuth这些主要区别在哪里呢?
参考:# cas 单点登录_5分钟明了单点登录SSO、OAuth、LDAP、CAS的流程与应用
- OAuth协议能广泛应用于互联网中,基于大企业的巨大用户量,能减少小网站的注册推广成本,并且能做到更加便捷的资源共享。
- LDAP协议适用于企业用户使用,通过LDAP协议,能较好地管理员工在公司各系统之间的授权与访问。
- CAS模型,作为权威机构开发的系统,具有很好的兼容性与安全性,广泛应用于各大高校等大型组织,能很好地完成大量系统的对接与大量人员的使用。