servlet-authentication-passwords-storage-ldap.md 16.4 KB
Newer Older
dallascao's avatar
dallascao 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
# LDAP 身份验证

LDAP 通常被组织用作用户信息的中心存储库和身份验证服务。它还可以用于存储应用程序用户的角色信息。

Spring 当安全性被配置为[接受用户名/密码](index.html#servlet-authentication-unpwd-input)以进行身份验证时,安全性使用基于 LDAP 的身份验证。然而,尽管利用用户名/密码进行身份验证,但它并未使用`UserDetailsService`进行集成,因为在[绑定身份验证](#servlet-authentication-ldap-bind)中,LDAP 服务器不返回密码,因此应用程序无法对密码进行验证。

对于如何配置 LDAP 服务器,有许多不同的场景,因此 Spring Security 的 LDAP 提供者是完全可配置的。它使用独立的策略接口来进行身份验证和角色检索,并提供可配置为处理各种情况的默认实现。

## 先决条件

在尝试使用 Spring 安全性之前,你应该熟悉 LDAP。下面的链接很好地介绍了所涉及的概念,并提供了使用免费的 LDAP 服务器 OpenLDAP 设置目录的指南:[https://www.zytrax.com/books/ldap/](https://www.zytrax.com/books/ldap/)。熟悉一些用于从 爪哇 访问 LDAP 的 JNDI API 也可能是有用的。在 LDAP 提供程序中,我们不使用任何第三方 LDAP 库(Mozilla、JLDAP 等),但是大量使用了 Spring LDAP,因此,如果你计划添加自己的定制,那么熟悉该项目可能会很有用。

在使用 LDAP 身份验证时,确保正确配置 LDAP 连接池非常重要。如果你不熟悉如何做到这一点,你可以参考[爪哇 LDAP 文档](https://docs.oracle.com/javase/jndi/tutorial/ldap/connect/config.html)

## 设置嵌入式 LDAP 服务器

你需要做的第一件事是确保你有一个 LDAP 服务器来指向你的配置。为了简单起见,通常最好从嵌入式 LDAP 服务器开始。 Spring 安全支持使用以下两种方式之一:

* [嵌入式无 boundid 服务器](#servlet-authentication-ldap-unboundid)

* [嵌入式 ApacheDS 服务器](#servlet-authentication-ldap-apacheds)

在下面的示例中,我们以`users.ldif`作为 Classpath 资源公开以下内容,以初始化嵌入的 LDAP 服务器,其中用户`user``admin`的密码都为`password`

users.ldif

```
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
```

### 嵌入式无 boundid 服务器

如果你希望使用[UnboundID](https://ldap.com/unboundid-ldap-sdk-for-java/),那么请指定以下依赖项:

例 1。无边界依赖项

Maven

```
<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
	<version>4.0.14</version>
	<scope>runtime</scope>
</dependency>
```

Gradle

```
depenendencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:4.0.14"
}
```

然后就可以配置嵌入式 LDAP 服务器了。

例 2。嵌入式 LDAP 服务器配置

爪哇

```
@Bean
UnboundIdContainer ldapContainer() {
	return new UnboundIdContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
```

XML

```
<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
```

Kotlin

```
@Bean
fun ldapContainer(): UnboundIdContainer {
    return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
}
```

### 嵌入式 ApacheDS 服务器

|   |Spring 安全性使用的是不再维护的 ApacheDS1.x。<br/>遗憾的是,ApacheDS2.x 只发布了里程碑版本,没有稳定的发布。<br/>一旦稳定的 ApacheDS2.x 版本可用,我们将考虑更新。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果你希望使用[Apache DS](https://directory.apache.org/apacheds/),那么请指定以下依赖项:

例 3。Apacheds 依赖项

Maven

```
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-core</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-server-jndi</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>
```

Gradle

```
depenendencies {
	runtimeOnly "org.apache.directory.server:apacheds-core:1.5.5"
	runtimeOnly "org.apache.directory.server:apacheds-server-jndi:1.5.5"
}
```

然后就可以配置嵌入式 LDAP 服务器了。

例 4。嵌入式 LDAP 服务器配置

爪哇

```
@Bean
ApacheDSContainer ldapContainer() {
	return new ApacheDSContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
```

XML

```
<b:bean class="org.springframework.security.ldap.server.ApacheDSContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
```

Kotlin

```
@Bean
fun ldapContainer(): ApacheDSContainer {
    return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif")
}
```

## LDAP ContextSource

一旦你有了一个 LDAP 服务器来指向你的配置,你就需要配置 Spring 安全性来指向一个应该用来验证用户身份的 LDAP 服务器。这是通过创建一个 LDAP`ContextSource`来完成的,它相当于一个 JDBC`DataSource`

例 5。LDAP 上下文源代码

爪哇

```
ContextSource contextSource(UnboundIdContainer container) {
	return new DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org");
}
```

XML

```
<ldap-server
	url="ldap://localhost:53389/dc=springframework,dc=org" />
```

Kotlin

```
fun contextSource(container: UnboundIdContainer): ContextSource {
    return DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org")
}
```

## 认证

Spring Security 的 LDAP 支持不使用[UserDetailsService](user-details-service.html#servlet-authentication-userdetailsservice),因为 LDAP BIND 身份验证不允许客户端读取密码,甚至不允许读取密码的散列版本。这意味着不可能读取密码,然后由 Spring Security 进行身份验证。

因此,LDAP 支持是使用`LdapAuthenticator`接口实现的。`LdapAuthenticator`还负责检索任何必需的用户属性。这是因为属性的权限可能取决于所使用的身份验证类型。例如,如果绑定为用户,则可能需要使用用户自己的权限来读取它们。

有两个`LdapAuthenticator`实现提供了 Spring 安全性:

* [使用绑定身份验证](#servlet-authentication-ldap-bind)

* [使用密码身份验证](#servlet-authentication-ldap-pwd)

## 使用绑定身份验证

[绑定身份验证](https://ldap.com/the-ldap-bind-operation/)是使用 LDAP 对用户进行身份验证的最常见机制。在 BIND 身份验证中,用户凭据(即用户名/密码)被提交给 LDAP 服务器,由该服务器对其进行身份验证。使用绑定身份验证的好处是,用户的秘密(即密码)不需要暴露给客户端,这有助于保护它们不被泄露。

可以在下面找到绑定身份验证配置的示例。

例 6。绑定身份验证

爪哇

```
@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	BindAuthenticator authenticator = new BindAuthenticator(contextSource);
	authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}
```

XML

```
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"/>
```

Kotlin

```
@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): BindAuthenticator {
    val authenticator = BindAuthenticator(contextSource)
    authenticator.setUserDnPatterns(arrayOf("uid={0},ou=people"))
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}
```

这个简单的示例将通过在提供的模式中替换用户登录名并尝试将该用户与登录密码绑定为该用户来获得用户的 DN。如果你的所有用户都存储在目录中的一个节点下,则可以这样做。如果你希望配置一个 LDAP 搜索过滤器来定位用户,那么可以使用以下方法:

例 7。用搜索筛选器绑定身份验证

爪哇

```
@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	String searchBase = "ou=people";
	String filter = "(uid={0})";
	FilterBasedLdapUserSearch search =
		new FilterBasedLdapUserSearch(searchBase, filter, contextSource);
	BindAuthenticator authenticator = new BindAuthenticator(contextSource);
	authenticator.setUserSearch(search);
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}
```

XML

```
<ldap-authentication-provider
		user-search-filter="(uid={0})"
	user-search-base="ou=people"/>
```

Kotlin

```
@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): BindAuthenticator {
    val searchBase = "ou=people"
    val filter = "(uid={0})"
    val search = FilterBasedLdapUserSearch(searchBase, filter, contextSource)
    val authenticator = BindAuthenticator(contextSource)
    authenticator.setUserSearch(search)
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}
```

如果与`ContextSource`[以上定义](#servlet-authentication-ldap-contextsource)一起使用,则将在 dn`ou=people,dc=springframework,dc=org`下使用`(uid={0})`作为过滤器执行搜索。同样,用户登录名被过滤器名中的参数所代替,因此它将搜索具有`uid`属性的条目,该属性等于用户名。如果没有提供用户搜索库,搜索将从根目录执行。

## 使用密码身份验证

密码比较是指将用户提供的密码与存储库中存储的密码进行比较。这可以通过检索 Password 属性的值并在本地进行检查来完成,也可以通过执行 LDAP“Compare”操作来完成,在该操作中,提供的密码被传递给服务器进行比较,而不会检索到真正的密码值。当密码被随机 SALT 正确散列时,无法进行 LDAP 比较。

例 8。最小密码比较配置

爪哇

```
@Bean
PasswordComparisonAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	return new PasswordComparisonAuthenticator(contextSource);
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}
```

XML

```
<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare />
</ldap-authentication-provider>
```

Kotlin

```
@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): PasswordComparisonAuthenticator {
    return PasswordComparisonAuthenticator(contextSource)
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}
```

可以在下面找到带有一些自定义功能的更高级配置。

例 9。密码比较配置

爪哇

```
@Bean
PasswordComparisonAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	PasswordComparisonAuthenticator authenticator =
		new PasswordComparisonAuthenticator(contextSource);
	authenticator.setPasswordAttributeName("pwd"); (1)
	authenticator.setPasswordEncoder(new BCryptPasswordEncoder()); (2)
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}
```

XML

```
<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare password-attribute="pwd"> (1)
		<password-encoder ref="passwordEncoder" /> (2)
	</password-compare>
</ldap-authentication-provider>
<b:bean id="passwordEncoder"
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
```

Kotlin

```
@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): PasswordComparisonAuthenticator {
    val authenticator = PasswordComparisonAuthenticator(contextSource)
    authenticator.setPasswordAttributeName("pwd") (1)
    authenticator.setPasswordEncoder(BCryptPasswordEncoder()) (2)
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}
```

|**1**|将 password 属性指定为`pwd`|
|-----|---------------------------------------|
|**2**|使用`BCryptPasswordEncoder`|

## ldapAuthoritiesPopulator

Spring 安全性的`LdapAuthoritiesPopulator`用于确定将为用户返回什么授权。

例 10。ldapAuthoritiesPopulator 配置

Java

```
@Bean
LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
	String groupSearchBase = "";
	DefaultLdapAuthoritiesPopulator authorities =
		new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
	authorities.setGroupSearchFilter("member={0}");
	return authorities;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authorities) {
	return new LdapAuthenticationProvider(authenticator, authorities);
}
```

XML

```
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"
	group-search-filter="member={0}"/>
```

Kotlin

```
@Bean
fun authorities(contextSource: BaseLdapPathContextSource): LdapAuthoritiesPopulator {
    val groupSearchBase = ""
    val authorities = DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase)
    authorities.setGroupSearchFilter("member={0}")
    return authorities
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator, authorities: LdapAuthoritiesPopulator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator, authorities)
}
```

## 活动目录

Active Directory 支持其自己的非标准身份验证选项,并且正常的使用模式与标准`LdapAuthenticationProvider`不太匹配。通常,身份验证是使用域用户名(形式为`[[email protected]](/cdn-cgi/l/email-protection)`)执行的,而不是使用 LDAP 专有名称。 Spring 为了使这更容易,Security 有一个身份验证提供者,该提供者是为典型的活动目录设置定制的。

配置`ActiveDirectoryLdapAuthenticationProvider`非常简单。你只需要提供域名和一个 LDAP URL,该 URL 提供服务器的地址<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。下面是一个配置示例:

例 11。活动目录配置示例

Java

```
@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
	return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}
```

XML

```
<bean id="authenticationProvider"
        class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
	<constructor-arg value="example.com" />
	<constructor-arg value="ldap://company.example.com/" />
</bean>
```

Kotlin

```
@Bean
fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
    return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
}
```

---

[1](#_footnoteref_1)。还可以使用 DNS 查找获得服务器的 IP 地址。这是目前不支持的,但希望将在未来的版本。

[DAoAuthenticationProvider](dao-authentication-provider.html)[Session Management](../session-management.html)