存儲機制(Storage Mechanisms)
每種支持的讀取用戶名和密碼的機制都可以使用任何支持的存儲機制:
- Simple Storage with In-Memory Authentication
- Relational Databases with JDBC Authentication
- Custom data stores with UserDetailsService
- LDAP storage with LDAP Authentication
In-Memory Authentication
Spring Security 的 InMemoryUserDetailsManager
實現了 UserDetailsService,以支持存儲在內存中的基于用戶名/密碼的身份驗證。InmemyUserDetailsManager
通過實現 UserDetailsManager 接口提供對 UserDetails 的管理。當 SpringSecurity 配置為接受用戶名和密碼進行身份驗證時,將使用基于 UserDetails 的身份驗證。
在下面的示例中,我們使用 Spring Boot CLI 對密碼的密碼值進行編碼,并得到{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW
:
InMemoryUserDetailsManager Java Configuration
@Bean
public UserDetailsService users() {UserDetails user = User.builder().username("user").password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW").roles("USER").build();UserDetails admin = User.builder().username("admin").password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW").roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);
}
前面的示例以安全的格式存儲密碼,但在初始體驗方面還有很多不足之處。
在下面的示例中,我們使用 User.withDefaultPasswordEncoder 來確保存儲在內存中的密碼受到保護。但是,它不能通過反編譯源代碼來防止獲取密碼。因此,User.withDefaultPasswordEncoder 只能用于“入門”,而不能用于生產。
InMemoryUserDetailsManager with User.withDefaultPasswordEncoder
@Bean
public UserDetailsService users() {// The builder will ensure the passwords are encoded before saving in memoryUserBuilder users = User.withDefaultPasswordEncoder();UserDetails user = users.username("user").password("password").roles("USER").build();UserDetails admin = users.username("admin").password("password").roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);
}
在基于 XML 的配置中使用 User.withDefaultPasswordEncoder 沒有簡單的方法。對于演示或剛開始,您可以選擇以{ noop }作為密碼的前綴來表示不應該使用編碼:
<user-service><user name="user"password="{noop}password"authorities="ROLE_USER" /><user name="admin"password="{noop}password"authorities="ROLE_USER,ROLE_ADMIN" />
</user-service>
JDBC Authentication
Spring Security 的 JdbcDaImpl 實現了 UserDetailsService,以支持使用 JDBC 檢索的基于用戶名和密碼的身份驗證。JdbcUserDetailsManager 擴展了 JdbcDaImpl,通過 UserDetailsManager 接口提供對 UserDetails 的管理。Spring Security 將基于 UserDetails 的身份驗證配置為接受用戶名/密碼進行身份驗證時,將使用該身份驗證。
在下面的章節中,我們將討論:
- Spring Security JDBC Authentication使用的默認架構
- 設置數據源
- JdbcUserDetailsManager Bean
默認架構(Default Schema)
SpringSecurity 為基于 JDBC 的身份驗證提供默認查詢。本節提供與默認查詢一起使用的相應默認架構。您需要調整模式,使其與所使用的查詢和本地數據庫的任何定制相匹配。
用戶架構(User Schema)
JdbcDaImpl 需要表來加載用戶的密碼、帳戶狀態(啟用或禁用)和權限(角色)列表。默認模式也公開為一個名為 org/springFramework/security/core/userDetails/jdbc/users.ddl 的類路徑資源。
Default User Schema
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null
);create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
Oracle 是一種流行的數據庫選擇,但需要一種略有不同的模式:
Default User Schema for Oracle Databases
CREATE TABLE USERS (USERNAME NVARCHAR2(128) PRIMARY KEY,PASSWORD NVARCHAR2(128) NOT NULL,ENABLED CHAR(1) CHECK (ENABLED IN ('Y','N') ) NOT NULL
);CREATE TABLE AUTHORITIES (USERNAME NVARCHAR2(128) NOT NULL,AUTHORITY NVARCHAR2(128) NOT NULL
);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;
組結構(Group Schema)
如果應用程序使用組,則需要提供組架構:
Default Group Schema
create table groups (id bigint generated by default as identity(start with 0) primary key,group_name varchar_ignorecase(50) not null
);create table group_authorities (group_id bigint not null,authority varchar(50) not null,constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);create table group_members (id bigint generated by default as identity(start with 0) primary key,username varchar(50) not null,group_id bigint not null,constraint fk_group_members_group foreign key(group_id) references groups(id)
);
設置數據源(Setting up a DataSource)
在配置 JdbcUserDetailsManager 之前,必須創建一個 DataSource。在我們的示例中,我們設置了一個用默認用戶架構初始化的嵌入式 DataSource。
@Bean
DataSource dataSource() {return new EmbeddedDatabaseBuilder().setType(H2).addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION).build();
}
JdbcUserDetailsManager Bean
在這個示例中,我們使用 Spring Boot CLI 對密碼的密碼值進行編碼,并獲得{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW
的編碼密碼。有關如何存儲密碼的詳細信息,請參閱 PasswordEncoder 部分。
JdbcUserDetailsManager
@Bean
UserDetailsManager users(DataSource dataSource) {UserDetails user = User.builder().username("user").password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW").roles("USER").build();UserDetails admin = User.builder().username("admin").password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW").roles("USER", "ADMIN").build();JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);users.createUser(user);users.createUser(admin);return users;
}
UserDetails
UserDetails 由 UserDetailsService 返回。DauAuthenticationProvider 驗證 UserDetails,然后返回一個 Authentication,其主體是由配置的 UserDetailsService 返回的 UserDetails。
UserDetailsService
DataAuthenticationProvider 使用 UserDetailsService 檢索用戶名、密碼和其他屬性,以便使用用戶名和密碼進行身份驗證。Spring Security 提供了 UserDetailsService 的內存、 JDBC 和緩存實現。
可以通過將自定義 UserDetailsService 公開為 bean 來定義自定義身份驗證。例如,下面的清單假設 CustomUserDetailsService 實現 UserDetailsService,從而自定義身份驗證:
只有在沒有填充 AuthenticationManagerBuilder 且沒有定義 AuthenticationProviderBean 的情況下才會使用。
Custom UserDetailsService Bean
@Bean
CustomUserDetailsService customUserDetailsService() {return new CustomUserDetailsService();
}
PasswordEncoder
SpringSecurity 的 servlet 支持包括通過集成 PasswordEncoder 來安全地存儲密碼。您可以通過公開 PasswordEncoderBean 來自定義 Spring Security 使用的 PasswordEncoder 實現。
DaoAuthenticationProvider
DauAuthenticationProvider 是一個 AuthenticationProvider 實現,它使用 UserDetailsService 和 PasswordEncoder 對用戶名和密碼進行身份驗證。
本節研究 DauAuthenticationProvider 如何在 SpringSecurity 中工作。下圖以“閱讀用戶名和密碼”部分中的數字解釋了 AuthenticationManager 的工作原理。
- Reading the Username & Password 部分中的身份驗證篩選器將 UsernamePasswordAuthenticationToken 傳遞給 AuthenticationManager,后者由 ProviderManager 實現。
- ProviderManager 配置為使用類型為 DauAuthenticationProvider 的 AuthenticationProvider。
- DauAuthenticationProvider 從 UserDetailsService 查找 UserDetails。
- DauAuthenticationProvider 使用 PasswordEncoder 驗證在上一步中返回的 UserDetails 上的密碼。
- 身份驗證成功后,返回的身份驗證類型為 UsernamePasswordAuthenticationToken,其主體是由配置的 UserDetailsService 返回的 UserDetails。最終,身份驗證篩選器將在 SecurityContextHolder 上設置返回的 UsernamePasswordAuthenticationToken。
LDAP Authentication
LDAP (輕型目錄訪問協議)經常被組織用作用戶信息的中央存儲庫和身份驗證服務。它還可用于存儲應用程序用戶的角色信息。
Spring Security 的基于 LDAP 的身份驗證在配置為接受用戶名/密碼進行身份驗證時使用。然而,盡管使用用戶名和密碼進行身份驗證,它并不使用 UserDetailsService,因為在綁定身份驗證中,LDAP 服務器不會返回密碼,因此應用程序無法進行密碼驗證。
對于如何配置 LDAP 服務器,有許多不同的場景,因此 Spring Security 的 LDAP 提供程序是完全可配置的。它使用單獨的策略接口進行身份驗證和角色檢索,并提供默認實現,可以將其配置為處理各種情況。
Required Dependencies
Spring Security LDAP Dependencies
<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>
Prerequisites
在嘗試將 LDAP 與 SpringSecurity 一起使用之前,您應該熟悉 LDAP。以下連結提供有關概念的簡介,以及使用免費 LDAP 伺服器(OpenLDAP)建立目錄的指引: www.zytrax.com/books/LDAP/。熟悉用于從 Java 訪問 LDAP 的 JNDI API 也很有用。我們在 LDAP 提供商中不使用任何第三方 LDAP 庫(Mozilla、 JLDAP 或其他) ,而是大量使用的是 Spring LDAP,所以如果您計劃添加自己的定制,對該項目有一些了解可能會有用。
在使用 LDAP 身份驗證時,應確保正確配置 LDAP 連接池。如果您不熟悉如何這樣做,請參閱 JavaLDAP 文檔。
設置嵌入式 LDAP 服務器(Setting up an Embedded LDAP Server)
您需要做的第一件事情是確保您有一個 LDAP 服務器來指向您的配置。
您需要做的第一件事情是確保您有一個 LDAP 服務器來指向您的配置。為了簡單起見,最好從嵌入式 LDAP 服務器開始。Spring Security 支持使用以下兩種方式:
- Embedded UnboundID Server
- Embedded ApacheDS Server
在下面的示例中,我們將 users.ldif 作為類路徑資源公開,以使用兩個用戶(user 和 admin)初始化嵌入式 LDAP 服務器,這兩個用戶都有密碼:
users.ldif
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groupsdn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: peopledn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: passworddn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: passworddn: 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=orgdn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
member: uid=admin,ou=people,dc=springframework,dc=org
Embedded UnboundID Server
如果希望使用 Unbound ID,請指定以下依賴項:
UnboundID Dependencies
<dependency><groupId>com.unboundid</groupId><artifactId>unboundid-ldapsdk</artifactId><version>6.0.11</version><scope>runtime</scope>
</dependency>
然后可以使用 EmbeddedLdapServerContextSourceFactoryBean 配置嵌入式 LDAP 服務器。這將指示 Spring Security 啟動內存中的 LDAP 服務器:
Embedded LDAP Server Configuration
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
或者,可以手動配置嵌入式 LDAP 服務器。如果選擇這種方法,您將負責管理嵌入式 LDAP 服務器的生命周期。
Explicit Embedded LDAP Server Configuration
@Bean
UnboundIdContainer ldapContainer() {return new UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif");
}
Embedded ApacheDS Server
Spring Security 使用 ApacheDS 1.x,不再進行維護。不幸的是,ApacheDS 2.x 只發布了里程碑版本,沒有穩定的版本。一旦 ApacheDS 2.x 的穩定版本可用,我們將考慮更新。
如果您希望使用 Apache DS,請指定以下依賴項:
ApacheDS Dependencies
<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>
然后可以配置嵌入式 LDAP 服務器:
@Bean
ApacheDSContainer ldapContainer() {return new ApacheDSContainer("dc=springframework,dc=org","classpath:users.ldif");
}
LDAP ContextSource
一旦有了指向配置的 LDAP 服務器,就需要將 Spring Security 配置為指向應該用于身份驗證用戶的 LDAP 服務器。為此,創建一個 LDAP ContextSource (相當于 JDBC DataSource)。如果已經配置了 EmbeddedLdapServerContextSourceFactoryBean,Spring Security 將創建指向嵌入式 LDAP 服務器的 LDAP ContextSource。
LDAP Context Source with Embedded LDAP Server
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();contextSourceFactoryBean.setPort(0);return contextSourceFactoryBean;
}
或者,可以顯式配置 LDAP ContextSource 以連接到提供的 LDAP 服務器:
LDAP Context Source
ContextSource contextSource(UnboundIdContainer container) {return new DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org");
}
Authentication
Spring Security 的 LDAP 支持不使用 UserDetailsService,因為 LDAP 綁定身份驗證不允許客戶端讀取密碼,甚至不允許密碼的散列版本。這意味著沒有辦法讀取密碼,然后由 SpringSecurity 進行身份驗證。
因此,LDAP 支持是通過 LdapAuthenticator 接口實現的。LdapAuthenticator 接口還負責檢索任何必需的用戶屬性。這是因為對屬性的權限可能取決于所使用的身份驗證類型。例如,如果綁定為用戶,則可能需要使用用戶自己的權限讀取屬性。
Spring Security 提供了兩個 LdapAuthenticator 實現:
- Using Bind Authentication
- Using Password Authentication
Using Bind Authentication
綁定身份驗證是使用 LDAP 對用戶進行身份驗證的最常用機制。在綁定身份驗證中,用戶的憑據(用戶名和密碼)被提交給 LDAP 服務器,LDAP 服務器對它們進行身份驗證。使用綁定身份驗證的優點是不需要向客戶機公開用戶的秘密(密碼) ,這有助于保護它們不被泄露。
下面的示例演示綁定身份驗證配置:
Bind Authentication
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);factory.setUserDnPatterns("uid={0},ou=people");return factory.createAuthenticationManager();
}
前面的簡單示例將通過在提供的模式中替換用戶登錄名并嘗試將該用戶與登錄密碼綁定來獲取該用戶的 DN。如果所有用戶都存儲在目錄中的單個節點下,則可以這樣做。如果希望配置 LDAP 搜索篩選器來定位用戶,可以使用以下方法:
Bind Authentication with Search Filter
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);factory.setUserSearchFilter("(uid={0})");factory.setUserSearchBase("ou=people");return factory.createAuthenticationManager();
}
如果與前面顯示的 ContextSource 定義一起使用,這將使用(uid = {0})作為過濾器在 DN ou = people,dc = springFramework,dc = org 下執行搜索。同樣,用戶登錄名替代了篩選器名稱中的參數,因此它搜索的條目的 uid 屬性與用戶名相等。如果未提供用戶搜索基,則從根目錄執行搜索。
Using Password Authentication
密碼比較是將用戶提供的密碼與存儲庫中存儲的密碼進行比較。這可以通過檢索 password 屬性的值并在本地進行檢查來完成,也可以通過執行 LDAP 的“比較”操作來完成,其中提供的密碼被傳遞給服務器進行比較,并且永遠不會檢索到真正的密碼值。如果密碼使用隨機的 salt 進行了正確的散列,則無法進行 LDAP 比較。
Minimal Password Compare Configuration
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(contextSource, NoOpPasswordEncoder.getInstance());factory.setUserDnPatterns("uid={0},ou=people");return factory.createAuthenticationManager();
}
下面的示例顯示了一個更高級的配置,其中包含一些自定義項:
Password Compare Configuration
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(contextSource, new BCryptPasswordEncoder());factory.setUserDnPatterns("uid={0},ou=people");factory.setPasswordAttribute("pwd");return factory.createAuthenticationManager();
}
- 將 password 屬性指定為 pwd。
LdapAuthoritiesPopulator
SpringSecurity 的 LdapAuthoritiesPopator 用于確定為用戶返回哪些權限。下面的示例顯示如何配置 LdapAuthoritiesPopator:
LdapAuthoritiesPopulator Configuration
@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();
}
Active Directory
ActiveDirectory 支持自己的非標準身份驗證選項,正常的使用模式與標準的 LdapAuthenticationProvider 不太匹配。通常,身份驗證是通過使用域用戶名(以 user@domain 的形式)執行的,而不是使用 LDAP 專有名稱。為了簡化這一點,SpringSecurity 有一個身份驗證提供程序,該程序是為典型的 ActiveDirectory 設置定制的。
配置 ActiveDirectoryLdapAuthenticationProvider 非常簡單。您只需提供域名和提供服務器地址的 LDAP URL。
也可以通過 DNS 查找獲得服務器的 IP 地址。目前還不支持,但希望在將來的版本中會支持。
下面的示例配置 ActiveDirectory:
Example Active Directory Configuration
@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}