# UserDetailsService
# 1. 基本概念
AuthenticationManager / AuthenticationProvider
AuthenticationManager 是 “表面上” 的做认证和鉴权比对工作的那个人;AuthenticationProvider 是 “实际上” 的做认证和鉴权比对工作的那个人。
从命名上很容易看出,Provider 受 ProviderManager 的管理,ProviderManager 调用 Provider 进行认证和鉴权的比对工作。
AuthenticationManager 和 AuthenticationProvider ,我们用到的和常见的实现类分别是:ProvierderManager 和 DaoAuthenticationProvider 。
UserDetailService
虽然 AuthenticationManager / AuthenticationProvider 负责进行用户名和密码的比对工作,但是它并不清楚用户名和密码的『标准答案』,而标准答案则是由 UserDetailService 来提供。
简单来说,UserDetailService 负责提供标准答案 ,以供 AuthenticationProvider 使用。
UserDetails
UserDetails 它是存放用户 “认证信息” 和 “权限信息” 的标准答案的 “容器” ,它也是 UserDetailService “应该” 返回的内容。
PasswordEncoder
Spring Security 要求密码不能是明文,必须经过加密器加密。这样,AuthenticationProvider 在做比对时,就必须知道『当初』密码时使用哪种加密器加密的。所以,AuthenticationProvider 除了要向 UserDetailsService 『要』用户名密码的标准答案之外,它还需要知道配套的加密算法(加密器)是什么。
# 2. UserDetailsService 和 UserDetails
之前有提到过,UserDetailsService 负责提供用户信息的 “标准答案” ,供 AuthenticationProvider 来比对。Spring Security 提供了 2 个内置的 UserDetailsService 的实现类:InMemoryUserDetailsManager 和 JdbcDaoImpl 。不过在实际项目中,通常我们并不会使用到它俩。
非常别扭的一点是:从名字上,你根本看不出 InMemoryUserDetailsManager 和 JdbcDaoImpl 是 UserDetailsService 的实现类。
UserDetails 是 UserDetailsService 的 “返回” 结果。Spring Security 要求 UserDetailsService 将用户信息的 “标准答案” 必须封装到一个 UserDetails 对象中,返回给 AuthenticationProvider 使用(做比对工作)。
我们可以直接使用 Spring Security 内置的 UserDetails 的实现类:User 。
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 模拟注册时的加密效果。
String password = NoOpPasswordEncoder.getInstance().encode("123");
// 硬编码用户名、密码、角色权限信息。现实工作中并非如此。
return new User("tom", password, AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN")); // 这里 admin 的大小写有区别
}
}
ProviderManager/AuthenticationProvider 在做密码密码的比对工作时,会调用 UserDetailsService 的 .loadUserByUsername()
方法,并传入『用户名』,用以查询该用户的密码和权限信息。
UserDetails 中封装了用户登录过程中所需的全部信息:
方法 | 说明 |
---|---|
isAccountNonExpired isAccountNonLocked isCredentialsNonExpired | 暂时用不到,统一返回 true ,否则 Spring Security 会认为账号异常。 |
isEnabled | 配合数据库层面的逻辑删除功能,用来表示当前用户是否还存在、是否可用。 |
getPassword getUsername | 需要返回的内容显而易见。 |
getAuthorities | 用于返回用户的权限信息。这里的权限就这是指用户的角色。它的返回值类型是 Collection<? extends GrantedAuthority>,具体形式通常是:List<GrantedAuthority>,里面用来存储角色信息(或权限信息) |
return new User("tom", password,
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
SimpleGrantedAuthority 是 GrantedAuthority 的一个实现类,也是最常见最常用的和实现类。如果直接使用的话那就是 new SimpleGrantedAuthority("ROLE_USER")
。
注意
另外需要注意的一点是,在一套配置中如果你存在多个 UserDetailsService 的 Spring Bean将会影响 DaoAuthenticationProvider 的注入和使用,从而导致出现 No Provider ...
的异常。
# 3. 利用 InMemoryUserDetailsManager(了解)
InMemoryUserDetailsManager 是 Spring Security 内置的 UserDetailsService 的实现类之一。如果,你想验证、学习 Spring Security 的其他特性(知识点)而懒得去自定义一个 UserDetailsService 的实现类时,你可以利用它。
启用 InMemoryUserDetailsManager 很简单,只需要在 configure 方法中进行配置即可:
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("tommy").password(passwordEncoder().encode("123")).roles("admin")
.and()
.withUser("jerry").password(passwordEncoder().encode("123")).roles("user");
}
# 4. Spring Security 和 RBAC
虽然在 RBAC 模型中,用户的 “权限” 是 “角色” 的下一级,但是在 Spring Security 中,它是将角色和权限一视同仁的,即,Spring Security 不强求你的角色和权限有上下级的关系。
在 Spring Security 中角色和权限都属于 Authority 。不过,Spring Security 有个『人为约定』:
如果你的 Authority 指的是角色,那么角色(的标准答案)就需要以
ROLE_
开头;如果你的 Authority 指的是权限,那么权限(的标准答案)则不需要特定的开头。
在后续很多涉及『角色』的地方,Spring Security 都会对 ROLE_
做额外处理。
# 5. 配置使用自定义 UserDetailsService
@Slf4j
@Configuration
@SuppressWarnings("deprecation")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource // 依赖注入
private MyUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService) // 配置 UserDetailService
.passwordEncoder(passwordEncoder()) // 配置 PasswordEncoder
;
}
}
# 6. 最后的说明
这里有 2 点需要说明:
截至目前为止,我们暂时只涉及到了『认证』,还没有涉及到『鉴权』。
截至目前为止,我们还有一些配置没有自定义,仍然使用的是默认配置。