4.2.2 基于 JDBC 的用户存储
用户信息通常在关系数据库中维护,基于 JDBC 的用户存储似乎比较合适。下面的程序清单显示了如何配置 Spring Security,并将用户信息通过 JDBC 保存在关系型数据库中,来进行身份认证。
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource);
}
configure() 的这个实现在给定的 AuthenticationManagerBuilder 上调用 jdbcAuthentication()。然后,必须设置 DataSource,以便它知道如何访问数据库。这里使用的数据源是由自动装配提供的。
重写默认用户查询
虽然这个最小配置可以工作,但它对数据库模式做了一些假设。它期望已经存在某些表,用户数据将保存在这些表中。更具体地说,以下来自 Spring Security 内部的代码片段显示了在查找用户详细信息时将执行的 SQL 查询:
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
第一个查询检索用户的用户名、密码以及是否启用它们,此信息用于对用户进行身份验证;下一个查询查询用户授予的权限,以进行授权;最后一个查询查询作为组的成员授予用户的权限。
如果可以在数据库中定义和填充满足这些查询的表,那么就没有什么其他要做的了。但是,数据库很可能不是这样的,需要对查询进行更多的控制。在这种情况下,可以配置自己的查询。程序清单 4.4 自定义用户详情查询
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery(
"select username, authority from UserAuthorities " +
"where username=?");
}
在本例中,仅重写了身份验证和基本授权查询,也可以通过使用自定义查询调用 groupAuthoritiesByUsername() 来重写组权限查询。
在将默认 SQL 查询替换为自己设计的查询时,一定要遵守查询的基本约定。它们都以用户名作为唯一参数。身份验证查询选择用户名、密码和启用状态;授权查询选择包含用户名和授予的权限的零个或多个行的数据;组权限查询选择零个或多个行数据,每个行有一个 group id、一个组名和一个权限。
使用编码密码
以身份验证查询为重点,可以看到用户密码应该存储在数据库中。唯一的问题是,如果密码以纯文本形式存储,就会受到黑客的窥探。但是如果在数据库中对密码进行编码,身份验证将失败,因为它与用户提交的明文密码不匹配。
为了解决这个问题,你需要通过调用 passwordEncoder() 方法指定一个密码编码器:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery(
"select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
}
passwordEncoder() 方法接受 Spring Security 的 passwordEncoder 接口的任何实现。Spring Security 的加密模块包括几个这样的实现:
  • BCryptPasswordEncoder —— 采用 bcrypt 强哈希加密
  • NoOpPasswordEncoder —— 不应用任何编码
  • Pbkdf2PasswordEncoder —— 应用 PBKDF2 加密
  • SCryptPasswordEncoder —— 应用了 scrypt 散列加密
  • StandardPasswordEncoder —— 应用 SHA-256 散列加密
上述代码使用了 StandardPasswordEncoder。但是,如果没有现成的实现满足你的需求,你可以选择任何其他实现,甚至可以提供你自己的自定义实现。PasswordEncoder 接口相当简单:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
}
无论使用哪种密码编码器,重要的是要理解数据库中的密码永远不会被解码。相反,用户在登录时输入的密码使用相同的算法进行编码,然后将其与数据库中编码的密码进行比较。比较是在 PasswordEncoder 的 matches() 方法中执行的。
最后,将在数据库中维护 Taco Cloud 用户数据。但是,我没有使用 jdbcAuthentication(),而是想到了另一个身份验证选项。但在此之前,让我们先看看如何配置 Spring Security 以依赖于另一个常见的用户数据源:使用 LDAP(轻量级目录访问协议)接入的用户存储。
复制链接