javascript
Spring Security认证过程
2019獨角獸企業重金招聘Python工程師標準>>>
Spring Security是一個能夠為基于Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重復代碼的工作。
類圖
為了方便理解Spring Security認證流程,特意畫了如下的類圖,包含相關的核心認證類
概述
核心驗證器
AuthenticationManager
該對象提供了認證方法的入口,接收一個Authentiaton對象作為參數;
public interface AuthenticationManager {Authentication authenticate(Authentication authentication)throws AuthenticationException; }ProviderManager
它是 AuthenticationManager 的一個實現類,提供了基本的認證邏輯和方法;它包含了一個 List<AuthenticationProvider> 對象,通過 AuthenticationProvider 接口來擴展出不同的認證提供者(當Spring Security默認提供的實現類不能滿足需求的時候可以擴展AuthenticationProvider 覆蓋supports(Class<?> authentication)方法);
驗證邏輯
AuthenticationManager 接收 Authentication 對象作為參數,并通過 authenticate(Authentication) 方法對其進行驗證;AuthenticationProvider實現類用來支撐對 Authentication 對象的驗證動作;UsernamePasswordAuthenticationToken實現了 Authentication主要是將用戶輸入的用戶名和密碼進行封裝,并供給 AuthenticationManager 進行驗證;驗證完成以后將返回一個認證成功的 Authentication 對象;
Authentication
Authentication對象中的主要方法
public interface Authentication extends Principal, Serializable {//#1.權限結合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串權限集合Collection<? extends GrantedAuthority> getAuthorities();//#2.用戶名密碼認證時可以理解為密碼Object getCredentials();//#3.認證時包含的一些信息。Object getDetails();//#4.用戶名密碼認證時可理解時用戶名Object getPrincipal();#5.是否被認證,認證為true boolean isAuthenticated();#6.設置是否能被認證void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;ProviderManager
ProviderManager是AuthenticationManager的實現類,提供了基本認證實現邏輯和流程;
public Authentication authenticate(Authentication authentication)throws AuthenticationException {//#1.獲取當前的Authentication的認證類型Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;boolean debug = logger.isDebugEnabled();//#2.遍歷所有的providers使用supports方法判斷該provider是否支持當前的認證類型,不支持的話繼續遍歷for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {#3.支持的話調用provider的authenticat方法認證result = provider.authenticate(authentication);if (result != null) {#4.認證通過的話重新生成Authentication對應的TokencopyDetails(authentication, result);break;}}catch (AccountStatusException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;}catch (InternalAuthenticationServiceException e) {prepareException(e, authentication);throw e;}catch (AuthenticationException e) {lastException = e;}}if (result == null && parent != null) {// Allow the parent to try.try {#5.如果#1 沒有驗證通過,則使用父類型AuthenticationManager進行驗證result = parent.authenticate(authentication);}catch (ProviderNotFoundException e) {// ignore as we will throw below if no other exception occurred prior to// calling parent and the parent// may throw ProviderNotFound even though a provider in the child already// handled the request}catch (AuthenticationException e) {lastException = e;}}#6. 是否擦出敏感信息if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication((CredentialsContainer) result).eraseCredentials();}eventPublisher.publishAuthenticationSuccess(result);return result;}// Parent was null, or didn't authenticate (or throw an exception).if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}prepareException(lastException, authentication);throw lastException;}- 如果某一個 Provider 驗證成功,則跳出循環不再執行后續的驗證;
- 如果驗證成功,會將返回的 result 既 Authentication 對象進一步封裝為 Authentication Token; 比如 UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken 等;這些 Authentication Token 也都繼承自 Authentication 對象;
AuthenticationProvider
ProviderManager 通過 AuthenticationProvider 擴展出更多的驗證提供的方式;而 AuthenticationProvider 本身也就是一個接口,從類圖中我們可以看出它的實現類AbstractUserDetailsAuthenticationProvider和AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider。DaoAuthenticationProvider是Spring Security中一個核心的Provider,對所有的數據庫提供了基本方法和入口。
DaoAuthenticationProvider
DaoAuthenticationProvider主要做了以下事情
private PasswordEncoder passwordEncoder;
2. 實現了 `AbstractUserDetailsAuthenticationProvider` 兩個抽象方法,1. 獲取用戶信息的擴展點```java protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {UserDetails loadedUser;try {loadedUser = this.getUserDetailsService().loadUserByUsername(username);} 主要是通過注入`UserDetailsService`接口對象,并調用其接口方法 `loadUserByUsername(String username)` 獲取得到相關的用戶信息。`UserDetailsService`接口非常重要。 2. 實現 additionalAuthenticationChecks 的驗證方法(主要驗證密碼);AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider為DaoAuthenticationProvider提供了基本的認證方法;
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// Determine usernameString username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {#1.獲取用戶信息由子類實現即DaoAuthenticationProvideruser = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {logger.debug("User '" + username + "' not found");if (hideUserNotFoundExceptions) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}else {throw notFound;}}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}try {#2.前檢查由DefaultPreAuthenticationChecks類實現(主要判斷當前用戶是否鎖定,過期,凍結User接口)preAuthenticationChecks.check(user);#3.子類實現additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException exception) {if (cacheWasUsed) {// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);preAuthenticationChecks.check(user);additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}else {throw exception;}}#4.檢測用戶密碼是否過期對應#2 的User接口postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}return createSuccessAuthentication(principalToReturn, authentication, user);}AbstractUserDetailsAuthenticationProvider主要實現了AuthenticationProvider的接口方法authenticate 并提供了相關的驗證邏輯;
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
2. 三步驗證工作1. preAuthenticationChecks2. additionalAuthenticationChecks(抽象方法,子類實現)3. postAuthenticationChecks 3. 將已通過驗證的用戶信息封裝成 UsernamePasswordAuthenticationToken 對象并返回;該對象封裝了用戶的身份信息,以及相應的權限信息,相關源碼如下,```java protected Authentication createSuccessAuthentication(Object principal,UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());return result;}UserDetailsService
UserDetailsService是一個接口,提供了一個方法
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }通過用戶名 username 調用方法 loadUserByUsername 返回了一個UserDetails接口對象(對應AbstractUserDetailsAuthenticationProvider的三步驗證方法);
public interface UserDetails extends Serializable {#1.權限集合Collection<? extends GrantedAuthority> getAuthorities();#2.密碼 String getPassword();#3.用戶民String getUsername();#4.用戶是否過期boolean isAccountNonExpired();#5.是否鎖定 boolean isAccountNonLocked();#6.用戶密碼是否過期 boolean isCredentialsNonExpired();#7.賬號是否可用(可理解為是否刪除)boolean isEnabled(); }Spring 為UserDetailsService默認提供了一個實現類 org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
JdbcUserDetailsManager
該實現類主要是提供基于JDBC對 User 進行增、刪、查、改的方法
public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager,GroupManager {// ~ Static fields/initializers// =====================================================================================// UserDetailsManager SQL#1.定義了一些列對數據庫操作的語句public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)";public static final String DEF_DELETE_USER_SQL = "delete from users where username = ?";public static final String DEF_UPDATE_USER_SQL = "update users set password = ?, enabled = ? where username = ?";public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)";public static final String DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?";public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?";InMemoryUserDetailsManager
該實現類主要是提供基于內存對 User 進行增、刪、查、改的方法 `public class InMemoryUserDetailsManager implements UserDetailsManager { protected final Log logger = LogFactory.getLog(getClass()); #1.用MAP 存儲 private final Map<String, MutableUserDetails> users = new HashMap<String, MutableUserDetails>();
private AuthenticationManager authenticationManager;public InMemoryUserDetailsManager() { }public InMemoryUserDetailsManager(Collection<UserDetails> users) {for (UserDetails user : users) {createUser(user);} }`總結
UserDetailsService接口作為橋梁,是DaoAuthenticationProvier與特定用戶信息來源進行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所構成;UserDetails和UserDetailsManager各司其責,一個是對基本用戶信息進行封裝,一個是對基本用戶信息進行管理;
特別注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用戶自定義的擴展點,我們可以繼承這些接口提供自己的讀取用戶來源和管理用戶的方法,比如我們可以自己實現一個 與特定 ORM 框架,比如 Mybatis 或者 Hibernate,相關的UserDetailsService和UserDetailsManager;
時序圖
轉載于:https://my.oschina.net/u/3656540/blog/1805899
總結
以上是生活随笔為你收集整理的Spring Security认证过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 纪录片《这里是香港》今日开播:感受传统和
- 下一篇: docker里images是什么意思