Spring Security内置了三种用户存储方式:
1、基于内存
2、基于数据库查询语句
3、自定义UserDetailsService服务来获取
这里的用户存储指的是,从哪里获取用户的信息
1、基于内存存储用户信息
在WebSecurityConfig类中配置:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("user").password("{noop}123456").roles("USER")
.and()
.withUser("admin").password("{noop}123456").roles("USER","ADMIN");
}
withUser()方法返回的是UserDetailsManagerConfigurer.UserDetailsBuilder,这个对象提供了多个进一步配置用户的方法,如上面的为用户设置密码的password()方法、授予用户多个角色权限的roles()方法。UserDetailsManagerConfigurer.UserDetailsBuilder对象的更多方法如下表:
roles()方法是authorities()方法的简写形式。roles()方法所给定的值都会添加一个“ROLE_”前缀,并将其作为权限授予给用户。如下的配置和上面的等价:
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").authorities("ROLE_USER")
.and()
.withUser("admin").password("{noop}password").authorities("ROLE_USER","ROLE_ADMIN");
2.基于数据库库sql查询语句
auth
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username,password,true from Spitter where username=?")
.authoritiesByUsernameQuery("select username,'ROLE_USER' from Spitter where username=?");
3、自定义UserDetailsService服务来获取用户信息
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(MyUserDetailsService());
}
UserDetailsService在身份认证中的作用
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
在Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀"ROLE_",而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。
所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等等。
GrantedAuthority接口的默认实现SimpleGrantedAuthority
注意,在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示"角色"的权限,在数据库中就带有"ROLE_"前缀了。所以authorities表中的视图可能是这样的。
角色和权限能否分开存储?角色能不能不带"ROLE_"前缀
当然可以分开存储,你可以定义两张表,一张存角色,一张存权限。但是你自定义UserDetailsService的时候,需要保证把这两张表的数据都取出来,放到UserDails的权限集合中。当然你数据库中存储的角色也可以不带"ROLE_"前缀,就像这样。
但是前面说到了,Security才不管你是角色,还是权限。它只比对字符串。
比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串"ROLE_ADMIN"。如果你从角色表中取出用户所拥有的角色时不加上"ROLE_"前缀,那验证的时候就匹配不上了。
所以角色信息存储的时候可以没有"ROLE_"前缀,但是包装成GrantedAuthority对象的时候必须要有。
// 设置添加用户信息,正常应该从数据库中读取
@Bean
UserDetailsService userDetailsService() {
//内存保存
/* InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("user1").password("{noop}123456")
.authorities("ROLE_USER").build());
userDetailsService.createUser(User.withUsername("user2").password("{noop}1234567")
.authorities("ROLE_USER").build());*/
//数据库加载
UserDetailsService userDetailsService = new UserDetailsService(){
//根据username加载用户基本信息,权限信息,角色信息 校验可以在自定义的daoAuhthenticationProvider中做
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AuthUser authUser = authUserMapper.selectByUsername(username);
if (authUser == null) {
throw new UsernameNotFoundException("账户不存在");
}
// MyUserDetails myUserDetails = new MyUserDetails(authUser) ;
//校验 这里只加载用户信息,尽量不要做校验 校验可以在自定义的daoAuhthenticationProvider中做
/* if (myUserDetails==null || myUserDetails.getAuthorities().isEmpty()) {
throw new UsernameNotFoundException("用户未分配任何权限");
}*/
return new User(authUser.getUsername(),authUser.getPassword(),this.getAuthorities(authUser));
}
public Collection<GrantedAuthority> getAuthorities(AuthUser authUser) {
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
//查用户角色
List<AuthRole> authRoles = authRoleMapper.selectByUserId(authUser.getId());
//查用户权限
List<AuthPermission> authPermissions = authPermissionMapper.selectByRoles(authRoles);
if (authRoles != null)
{
//角色权限
Set<String> roleStrs = authRoles.stream().map(AuthRole::getRoleName).collect(Collectors.toSet());
for (String code : roleStrs) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code);
authorities.add(authority);
}
//方法权限
Set<String> authStrs = authPermissions.stream().map(AuthPermission::getCode).collect(Collectors.toSet());
for (String code : authStrs) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code);
authorities.add(authority);
}
}
return authorities;
}
};
return userDetailsService;
}
说明:
这里的返回结果User就是UserDetails接口的实现类:
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
构造方案非常简单:
username 用户名
password 密码
authorities 权限
总结:
1、本章介绍了OAuth2.0用户信息可以有2种存储方式,一种是存放在内存中,一般只会在测试时使用;一种是基于数据库的持久化存储。
三种加载用户信息的方式:
- 直接基于内存中的用户信息去进行认证inMemoryAuthentication
- 采用sql语句从数据库加载用户信息
- 继承UserDetailsService方法,加载用户信息,推荐使用这种方法,最灵活通用。
github源码:
https://github.com/StarlightWANLI/oauth2.git
如果你还有其他OAuth2.0认证方面的想了解的内容,可以在底下评论留言。我会尽力抽空调研下。
最后,如果我写的文章对您能有些许帮助,请帮忙点赞、关注。