In-Memory Authentication
Spring Security 的 InMemoryUserDetailsManager 实现了 UserDetailsService,以支持存储在内存中的基于用户名/密码的身份验证。InMemoryUserDetailsManager 通过实现 UserDetailsManager 接口提供对 UserDetails 的管理。当 Spring Security 配置为接受用户名/密码进行身份验证时,它使用基于 UserDetails 的身份验证。
在这个示例中,我们使用 Spring Boot CLI 对 password 密码进行编码,并获得编码密码{ bcrypt } $2a $GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ. 0 FxO/BTk76klW。
@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 只能用于“入门”,不能用于生产。
@Bean
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
UserBuilder 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 的 JdbcDaoImpl 实现了 UserDetailsService,以支持使用 JDBC 检索的基于用户名/密码的身份验证。JdbcUserDetailsManager 扩展了 JdbcDaoImpl,通过 UserDetailsManager 接口提供对 UserDetails 的管理。当 Spring Security 配置为接受用户名/密码进行身份验证时,它使用基于 UserDetails 的身份验证。
Default Schema
Spring Security 为基于 JDBC 的身份验证提供默认查询。本节提供与默认查询一起使用的相应默认模式。您将需要调整模式以匹配所使用的查询和数据库方言的任何自定义。
User Schema
JdbcDaoImpl 需要表来为用户加载密码、帐户状态(启用或禁用)和权限(角色)列表。下面可以找到所需的默认模式。
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 是一个流行的数据库选择,但是需要稍微不同的模式。您可以在下面找到用户的默认 Oracle Schema。
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
如果您的应用程序正在利用组,则需要提供组模式。组的默认模式可以在下面找到。
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("classpath:org/springframework/security/core/userdetails/jdbc/users.ddl")
.build();
}
在生产环境中,您需要确保建立到外部数据库的连接。
JdbcUserDetailsManager Bean
在这个示例中,我们使用 Spring Boot CLI 对 password 密码进行编码,并获得编码密码{ bcrypt } $2a $GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ. 0 FxO/BTk76klW。有关如何存储密码的详细信息,请参阅 PasswordEncoder 部分。
@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
UserDetailsService 返回 UserDetails。DaoAuthenticationProvider 验证 UserDetails,然后返回一个身份验证,该身份验证的主体是已配置的 UserDetailsService 返回的 UserDetails。
UserDetailsService
UserDetailsService 被 DaoAuthenticationProvider 用于检索用户名、密码和其他属性,以便对用户名和密码进行身份验证。Spring Security 提供了 UserDetailsService 的内存和 JDBC 实现。
可以通过将自定义 UserDetailsService 公开为 bean 来定义自定义身份验证。例如,假设 CustomUserDetailsService 实现 UserDetailsService,下面将自定义身份验证:
@Bean
CustomUserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
PasswordEncoder
Spring Security的 Servlet 通过与 PasswordEncode 集成,支持安全地存储密码。定制Spring Security使用的PasswordEncode 实现可以通过公开 PasswordEncoderBean 来完成。
DaoAuthenticationProvider
DaoAuthenticationProvider 是一个 AuthenticationProvider 实现,它利用 UserDetailsService 和 PasswordEncoder 来对用户名和密码进行身份验证。
让我们来看看在 Spring Security 中 DaoAuthenticationProvider 是如何工作的。这个图解释了身份验证管理器如何工作的详细信息,见 用户名/密码认证。
- 通过读取 Username & Password 的身份验证过滤器将一个 UsernamePasswordAuthenticationToken 传递给 AuthenticationManager,后者由 ProviderManager 实现。
- ProviderManager 被配置为使用 DaoAuthenticationProvider 类型的 AuthenticationProvider。
- DaoAuthenticationProvider 从 UserDetailsService 查找 UserDetails。
- 然后,DaoAuthenticationProvider 使用 PasswordEncoder 验证前一步中返回的 UserDetails 上的密码。
- 身份验证成功后,返回的身份验证类型为 UsernamePasswordAuthenticationToken,其主体为已配置的 UserDetailsService 返回的 UserDetails。最终,身份验证筛选器将在 SecurityContextHolder 上设置返回的 UsernamePasswordAuthenticationToken。