UserDetailsService
#1. 基本概念
-
AuthenticationManager ,類似於 Shiro 中的 SecurityManager 。
它是 “表面上” 的做認證和鑒權比對工作的那個人,它是認證和鑒權比對工作的起點。
ProvierderManager 是 AuthenticationManager 的具體實現。
-
AuthenticationProvider ,類似於 Shiro 中的 Authenticator
它是 “實際上” 的做認證和鑒權比對工作的那個人。從命名上很容易看出,Provider 受 ProviderManager 的管理,ProviderManager 調用 Provider 進行認證和鑒權的比對工作。
我們最常用到 DaoAuthenticationProvider 是 AuthenticationProvider 的具體實現。
-
UserDetailService ,類似於 Shiro 中的 Realm 。
雖然 AuthenticationProvider 負責進行用戶名和密碼的比對工作,但是它並不清楚用戶名和密碼的『標准答案』,而標准答案則是由 UserDetailService 來提供。簡單來說,UserDetailService 負責提供標准答案,以供 AuthenticationProvider 使用。
-
UserDetails,類似於 Shiro 中的 AuthenticationInfo + AuthorizationInfo
UserDetails 它是存放用戶認證信息和權限信息的標准答案的 “容器” ,它也是 UserDetailService “應該” 返回的內容。和 Shiro 不同的是,Shiro 中是把認證和權限信息分開放的,而 UserDetails 則是塞到了一起。
-
PasswordEncoder,類似於 Shiro 中的 CredentialsMatcher 。
Spring Security 要求密碼不能是明文,必須經過加密器加密。這樣,AuthenticationProvider 在做比對時,就必須知道『當初』密碼時使用哪種加密器加密的。所以,AuthenticationProvider 除了要向 UserDetailsService 『要』用戶名密碼的標准答案之外,它還需要知道配套的加密算法(加密器)是什么。
#2. UserDetailsService 和 UserDetails
之前有提到過,UserDetailsService 類似於 Shiro 中的 Realm,負責提供用戶信息的 “標准答案” ,供 AuthenticationProvider 來比對。Spring Security 提供了 2 個內置的 UserDetailsService 的實現類:InMemoryUserDetailsManager 和 JdbcDaoImpl 。不過在實際項目中,通常我們並不會使用到它倆。
非常別扭的一點是:從名字上,你根本看不出 InMemoryUserDetailsManager 和 JdbcDaoImpl 是 UserDetailsService 的實現類。
如果說 UserDetailsService 的職責類似於 Shiro 中的 Realm,那么 UserDetails 的職責就是 Shiro 中的 AuthenticationInfo + AuthorizationInfo 。
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 ...
的異常。
#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 { // 這一步和 Shiro 中的 Realm 和 CredentialsMatcher 的綁定很像,讓兩者關聯。 auth.userDetailsService(userDetailsService) // 配置 UserDetailService .passwordEncoder(passwordEncoder()) // 配置 PasswordEncoder ; } }
#6. 最后的說明
這里有 2 點需要說明:
-
截至目前為止,我們暫時只涉及到了『認證』,還沒有涉及到『鑒權』。
-
截至目前為止,我們還有一些配置沒有自定義,仍然使用的是默認配置。