LDAP 認證
LDAP(輕型目錄訪問協議)通常被組織用作使用者資訊的中央儲存庫和身份驗證服務。它還可以用於儲存應用程式使用者的角色資訊。
當 Spring Security 配置為接受使用者名稱/密碼進行身份驗證時,Spring Security 會使用其基於 LDAP 的身份驗證。然而,儘管使用使用者名稱和密碼進行身份驗證,它不使用 UserDetailsService,因為在繫結認證中,LDAP 伺服器不返回密碼,所以應用程式無法執行密碼驗證。
LDAP 伺服器的配置場景多種多樣,因此 Spring Security 的 LDAP 提供程式是完全可配置的。它為身份驗證和角色檢索使用獨立的策略介面,並提供預設實現,這些實現可以配置以處理各種情況。
所需依賴項
首先,將 spring-security-ldap 依賴項新增到您的專案中。當使用 Spring Boot 時,新增以下依賴項
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
depenendencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap"
}
先決條件
在嘗試將 LDAP 與 Spring Security 一起使用之前,您應該熟悉 LDAP。以下連結提供了相關概念的良好介紹,以及使用免費 LDAP 伺服器 OpenLDAP 設定目錄的指南:www.zytrax.com/books/ldap/。熟悉用於從 Java 訪問 LDAP 的 JNDI API 也很有用。我們在 LDAP 提供程式中不使用任何第三方 LDAP 庫(Mozilla、JLDAP 或其他),但廣泛使用了 Spring LDAP,因此如果您計劃新增自己的自定義項,熟悉該專案可能會很有用。
使用 LDAP 認證時,應確保正確配置 LDAP 連線池。如果您不熟悉如何操作,請參閱 Java LDAP 文件。
設定嵌入式 LDAP 伺服器
您需要做的第一件事是確保您有一個 LDAP 伺服器可供您的配置指向。為了簡單起見,通常最好從嵌入式 LDAP 伺服器開始。Spring Security 支援使用以下任一方式
在以下示例中,我們將 users.ldif 作為類路徑資源公開,以使用兩個使用者 user 和 admin(兩者的密碼均為 password)初始化嵌入式 LDAP 伺服器
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
member: uid=admin,ou=people,dc=springframework,dc=org
member: uid=user,ou=people,dc=springframework,dc=org
dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
member: uid=admin,ou=people,dc=springframework,dc=org
嵌入式 UnboundID 伺服器
如果您希望使用 UnboundID,請指定以下依賴項
-
Maven
-
Gradle
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>7.0.3</version>
<scope>runtime</scope>
</dependency>
depenendencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:7.0.3"
}
然後,您可以使用 EmbeddedLdapServerContextSourceFactoryBean 配置嵌入式 LDAP 伺服器。這將指示 Spring Security 啟動一個記憶體中的 LDAP 伺服器
-
Java
-
Kotlin
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
}
或者,您可以手動配置嵌入式 LDAP 伺服器。如果您選擇此方法,您將負責管理嵌入式 LDAP 伺服器的生命週期。
-
Java
-
XML
-
Kotlin
@Bean
UnboundIdContainer ldapContainer() {
return new UnboundIdContainer("dc=springframework,dc=org",
"classpath:users.ldif");
}
<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
c:defaultPartitionSuffix="dc=springframework,dc=org"
c:ldif="classpath:users.ldif"/>
@Bean
fun ldapContainer(): UnboundIdContainer {
return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
}
嵌入式 ApacheDS 伺服器
Spring Security 7 移除了對 Apache DS 的支援。請改用 UnboundID。
LDAP ContextSource
一旦您有了指向配置的 LDAP 伺服器,您就需要配置 Spring Security 以指向用於驗證使用者的 LDAP 伺服器。為此,請建立一個 LDAP ContextSource(它相當於 JDBC DataSource)。如果您已經配置了 EmbeddedLdapServerContextSourceFactoryBean,Spring Security 將建立一個指向嵌入式 LDAP 伺服器的 LDAP ContextSource。
-
Java
-
Kotlin
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
contextSourceFactoryBean.setPort(0);
return contextSourceFactoryBean;
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
val contextSourceFactoryBean = EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
contextSourceFactoryBean.setPort(0)
return contextSourceFactoryBean
}
或者,您可以顯式配置 LDAP ContextSource 以連線到提供的 LDAP 伺服器
-
Java
-
XML
-
Kotlin
ContextSource contextSource(UnboundIdContainer container) {
return new DefaultSpringSecurityContextSource("ldap://:53389/dc=springframework,dc=org");
}
<ldap-server
url="ldap://:53389/dc=springframework,dc=org" />
fun contextSource(container: UnboundIdContainer): ContextSource {
return DefaultSpringSecurityContextSource("ldap://:53389/dc=springframework,dc=org")
}
認證
Spring Security 的 LDAP 支援不使用 UserDetailsService,因為 LDAP 繫結認證不允許客戶端讀取密碼或密碼的雜湊版本。這意味著 Spring Security 無法讀取密碼然後進行認證。
因此,LDAP 支援透過 LdapAuthenticator 介面實現。LdapAuthenticator 介面還負責檢索任何所需的使用者屬性。這是因為屬性上的許可權可能取決於所使用的身份驗證型別。例如,如果以使用者身份繫結,可能需要使用使用者自己的許可權讀取屬性。
Spring Security 提供了兩種 LdapAuthenticator 實現
使用繫結認證
繫結認證 是使用 LDAP 驗證使用者最常見的機制。在繫結認證中,使用者的憑據(使用者名稱和密碼)提交給 LDAP 伺服器,由伺服器進行認證。使用繫結認證的優點是使用者的秘密資訊(密碼)不需要暴露給客戶端,這有助於保護它們不被洩露。
以下示例顯示了繫結身份驗證配置
-
Java
-
XML
-
Kotlin
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
<ldap-authentication-provider
user-dn-pattern="uid={0},ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
val factory = LdapBindAuthenticationManagerFactory(contextSource)
factory.setUserDnPatterns("uid={0},ou=people")
return factory.createAuthenticationManager()
}
前面的簡單示例將透過替換提供的模式中的使用者登入名並嘗試使用登入密碼以該使用者身份進行繫結來獲取使用者的 DN。如果您的所有使用者都儲存在目錄中的單個節點下,則此方法是可行的。如果相反,您希望配置 LDAP 搜尋過濾器來查詢使用者,則可以使用以下內容
-
Java
-
XML
-
Kotlin
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserSearchFilter("(uid={0})");
factory.setUserSearchBase("ou=people");
return factory.createAuthenticationManager();
}
<ldap-authentication-provider
user-search-filter="(uid={0})"
user-search-base="ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
val factory = LdapBindAuthenticationManagerFactory(contextSource)
factory.setUserSearchFilter("(uid={0})")
factory.setUserSearchBase("ou=people")
return factory.createAuthenticationManager()
}
如果與前面所示的 ContextSource 定義一起使用,這將透過使用 (uid={0}) 作為過濾器在 DN ou=people,dc=springframework,dc=org 下執行搜尋。同樣,使用者登入名將替換過濾器名中的引數,因此它會搜尋 uid 屬性等於使用者名稱的條目。如果沒有提供使用者搜尋基礎,則從根目錄執行搜尋。
使用密碼認證
密碼比較是指將使用者提供的密碼與儲存在儲存庫中的密碼進行比較。這可以透過檢索密碼屬性的值並在本地檢查,或者執行 LDAP “比較”操作來完成,其中提供的密碼傳遞給伺服器進行比較,並且從不檢索真實的密碼值。當密碼使用隨機鹽正確雜湊時,無法進行 LDAP 比較。
-
Java
-
XML
-
Kotlin
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, NoOpPasswordEncoder.getInstance());
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
<ldap-authentication-provider
user-dn-pattern="uid={0},ou=people">
<password-compare />
</ldap-authentication-provider>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource?): AuthenticationManager? {
val factory = LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, NoOpPasswordEncoder.getInstance()
)
factory.setUserDnPatterns("uid={0},ou=people")
return factory.createAuthenticationManager()
}
以下示例展示了一個更高階的配置,帶有一些自定義項
-
Java
-
XML
-
Kotlin
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, new BCryptPasswordEncoder());
factory.setUserDnPatterns("uid={0},ou=people");
factory.setPasswordAttribute("pwd"); (1)
return factory.createAuthenticationManager();
}
<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" />
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
val factory = LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, BCryptPasswordEncoder()
)
factory.setUserDnPatterns("uid={0},ou=people")
factory.setPasswordAttribute("pwd") (1)
return factory.createAuthenticationManager()
}
| 1 | 將密碼屬性指定為 pwd。 |
LdapAuthoritiesPopulator
Spring Security 的 LdapAuthoritiesPopulator 用於確定為使用者返回哪些許可權。以下示例顯示瞭如何配置 LdapAuthoritiesPopulator
-
Java
-
XML
-
Kotlin
@Bean
LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
String groupSearchBase = "";
DefaultLdapAuthoritiesPopulator authorities =
new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
authorities.setGroupSearchFilter("member={0}");
return authorities;
}
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
factory.setLdapAuthoritiesPopulator(authorities);
return factory.createAuthenticationManager();
}
<ldap-authentication-provider
user-dn-pattern="uid={0},ou=people"
group-search-filter="member={0}"/>
@Bean
fun authorities(contextSource: BaseLdapPathContextSource): LdapAuthoritiesPopulator {
val groupSearchBase = ""
val authorities = DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase)
authorities.setGroupSearchFilter("member={0}")
return authorities
}
@Bean
fun authenticationManager(
contextSource: BaseLdapPathContextSource,
authorities: LdapAuthoritiesPopulator): AuthenticationManager {
val factory = LdapBindAuthenticationManagerFactory(contextSource)
factory.setUserDnPatterns("uid={0},ou=people")
factory.setLdapAuthoritiesPopulator(authorities)
return factory.createAuthenticationManager()
}
Active Directory
Active Directory 支援其自己的非標準認證選項,並且正常的使用模式與標準的 LdapAuthenticationProvider 不太契合。通常,認證是透過使用域名使用者名稱(形式為 user@domain)而不是使用 LDAP 專有名稱來執行的。為了簡化這一點,Spring Security 提供了一個認證提供者,它針對典型的 Active Directory 設定進行了定製。
配置 ActiveDirectoryLdapAuthenticationProvider 非常簡單。您只需提供域名和提供伺服器地址的 LDAP URL。
|
還可以透過 DNS 查詢獲取伺服器的 IP 地址。目前不支援此功能,但希望在將來的版本中提供。 |
以下示例配置了 Active Directory
-
Java
-
XML
-
Kotlin
@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}
<bean id="authenticationProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="example.com" />
<constructor-arg value="ldap://company.example.com/" />
</bean>
@Bean
fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
}