chapter09_保护Web应用_5_选择查询用户详细信息的服
-
有的时候需要对用户的信息进行存储,以便判断登录进系统时的权限。我们需要的是用户存储,在进行认证决策的时候,进行检索
-
Spring Security内置了了多种数据存储方式来认证用户,包括内存、关系型数据库、LDAP等
-
使用基于内存的用户存储
(1) 适用于开发、测试的场景,数据量小
(2) 要重载 WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder)方法,使用inMemoryAuthentication()方法进行基于内存的用户存储
示例 SecurityConfig.java
@Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"). and.withUser("admin").password("password").roles("USER", "ADMIN"); } }
(3) withUser方法添加新的用户,返回类型为 UserDetailsManagerConfigurer,这个对象可以进一步做一系列的用户信息细节配置,详见P259
(4) 使用and()方法可以继续添加新用户
-
基于RDBMS的用户存储与认证
(1) 示例 SecurityConfig.java
import javax.sql.DataSource; @Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Autowired DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication().dataSource(dataSource). usersByUsernameQuery("select username, password, true from Spitter " + "where username=?"). authoritiesByUsernameQuery("select username, 'ROLE_USER' from Spitter " + "where username=?"). passwordEncoder(new StandardPasswordEncoder()); } }
(2) 使用jdbcAuthentication()方法可以配置RDBMS型的用户存储,我们只需要自动装配@Autowired一个dataSource即可
(3) 当直接使用
auth.jdbcAuthentication().dataSource(dataSource);
时,会采用默认的SQL语句,在SpringSecurity内置的JdbcDaoImpl类中
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";
但是,当数据库和默认的表名等不统一时,需要重写和以上三个SQL语句关联的方法;
其中, usersByUsernameQuery()方法(认证查询)和DEF_USERS_BY_USERNAME_QUERY关联;authoritiesByUsernameQuery()方法(权限查询)和DEF_AUTHORITIES_BY_USERNAME_QUERY关联;groupAuthoritiesByUsername()方法(群组权限查询)和DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY关联
(4) 以上的3个方法在自己配置时,要按照规定的协议来配置:
所有的查询都将用户名username作为唯一的参数;
认证查询会选取用户名、密码、启用状态;
权限查询会选取用户名、权限信息;
群组权限查询会选取群组id、群组名称、权限;
即使数据库中包含上面要查询的某项,也要使用布尔值或字符串的方式进行填充(类似于"select username, password, true from Spitter where username=?"和"select username, 'ROLE_USER' from Spitter where username=?")
(5) 使用转码后的密码
用户的密码应该进行加密,然后存储在数据库中,使用passwordEncoder()方法就可以解决这个问题;
数据库中的密码永远不会被解码,而是用户登录时输入的密码使用相同的算法进行加密,然后和数据库中转码后的密码进行对比