spring-security(十四)UserDetailsService

阅读更多
前言:
  作为spring security的核心类,大多数的认证方式都会用到UserDetailsService和UserDetails这两个接口,本文会详细探讨下UserDetailsService及其实现类。

1. UserDetailsService接口
在spring security中,为了方便扩展,UserDetailsService接口被设计的极其简单,只包含一个方法
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

根据提供的用户名,查找对应的用户信息,只是查找信息,没有和认证相关的任何逻辑,是DaoAuthenticationProvider默认使用的用户信息加在类,返回的UserDetails也是一个接口。根据具体实现的不同,做用户信息查找时即有可能是大小写敏感,也有可能不敏感,所以UserDetails中的用户名并不一定和传入的用户名一致。在一些认证方式中,即便他们并不真的使用用户名和密码,也会用到UserDetailsService类,可能仅仅是想利用UserDetails对象中的GrantedAuthority信息来做权限判断(如LDAP、X.509、CAS等,这些认证系统自身承担着验证证书是否有效的任务)。
因为UserDetailsService接口是如此简单,我们很容易实现他,来自定义我们获取用户信息的逻辑,同时spring security也为我们提供了一些基本常见的实现。
2.基于内存的认证
自定义UserDetailsService从我们喜欢的一种数据持久化引擎中(文件、数据库等)获取数据并不麻烦,但有时候我们并不需要这么复杂的实现,例如我们仅仅是做一个spring security的原型、或者是在学习spring security,你并不想花时间来搭建一个数据库,此时就可以以使用基于内存的用户信息存储,使用java config配置用户信息如下:
	@Bean
	public UserDetailsService userDetailsService() {
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("user").password("password").roles("USER").build());
		manager.createUser(User.withUsername("admin").password("password").roles("ADMIN").build());
		return manager;
	}

这种方式还支持properties文件来配置
@Bean
	public UserDetailsService userDetailsService() throws FileNotFoundException, IOException {

		Properties properties = new Properties();
		properties.load(getClass().getClassLoader().getResourceAsStream("users.properties"));

		return new InMemoryUserDetailsManager(properties);
	}

properties的文件格式如下:
username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]

例如
 
jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled

还可以使用如下代码段来创建
@Autowired
	public void auth(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("admin").password("password").authorities("ROLE_ADMIN").and()
				.withUser("user").password("password").authorities("ROLE_USER");
	}

3.基于关系数据库的认证
3.1 spring security也为我们提供了一个从关系型数据库获取认证信息的实现-JdbcDaoImpl,在这个类内部使用了Spring JDBC,下面是一个具体使用的配置例子
@Bean
	public DruidDataSource dataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUsername("root");
		dataSource.setPassword("sso");
		dataSource.setName("chengf");
		dataSource.setUrl("jdbc:mysql://localhost:3306/chengf?useUnicode=true&characterEncoding=UTF-8");
		dataSource.setMaxActive(20);
		dataSource.setInitialSize(1);
		dataSource.setMaxWait(60000);
		dataSource.setMinIdle(1);
		dataSource.setTimeBetweenEvictionRunsMillis(60000);
		dataSource.setMinEvictableIdleTimeMillis(300000);
		dataSource.setValidationQuery("select 'x'");
		dataSource.setPoolPreparedStatements(true);
		dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
		return dataSource;
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource);
	}

完整的例子请参考 spring-security(四)java config-sample之jdbc
3.2 默认情况下,JdbcDaoImpl直接从数据库中获取某个特定用户对应的权限列表信息,在实际应用中我们可能会先将用户分组,再把具体的权限赋予某个分组,从而使这个组里面的用户都具有这些权限,JdbcDaoImpl也为我们提供了 分组的支持,默认情况下没有启用,如果想使用这个功能,可以通过如下设置
@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource).getUserDetailsService().setEnableGroups(true);;
	}

如果spring 默认的schema不能满足我们的业务需求(表结构的定义、信息的完整性),我们也可以利用JdbcDaoImpl,重写其中的查询语句,如我们想重写查询用户信息的语句可以如下配置
@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource).getUserDetailsService().setEnableGroups(true);;
		auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username,password,email,phone,address,enabled from my_user_table where username = ? ");
	}

如果默认实现和我们需求相差太大,简单修改配置已不能满足我们需求,我们可以自己实现加载用户信息的类,就是实现UserDetailsService,并把他作为bean注册到spring 中即可。

@Bean
	public DruidDataSource dataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUsername("root");
		dataSource.setPassword("sso");
		dataSource.setName("chengf");
		dataSource.setUrl("jdbc:mysql://localhost:3306/chengf?useUnicode=true&characterEncoding=UTF-8");
		dataSource.setMaxActive(20);
		return dataSource;
	}
	@Bean
	public UserDetailsService userDetailsService () {
		MyUserDetailsService userDetailsService = new MyUserDetailsService();
		userDetailsService.setDataSource(dataSource());
		return userDetailsService;
	}

这样在程序启动时,配置类InitializeUserDetailsManagerConfigurer加载时,会为我们创建一个DaoAuthenticationProvider,并且使用我们定义的UserDetailsService。具体过程可参考
spring-security(二)java config加载机制-@EnableGlobalAuthentication

你可能感兴趣的:(spring,security)