Spring Security是一個可以爲基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組能夠在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,爲應用系統提供聲明式的安全訪問控制功能,減小了爲企業系統安全控制編寫大量重複代碼的工做。java
爲了方便理解Spring Security認證流程,特地畫了以下的類圖,包含相關的核心認證類 git
核心驗證器github
該對象提供了認證方法的入口,接收一個Authentiaton
對象做爲參數;算法
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
它是 AuthenticationManager
的一個實現類,提供了基本的認證邏輯和方法;它包含了一個 List<AuthenticationProvider>
對象,經過 AuthenticationProvider 接口來擴展出不一樣的認證提供者(當Spring Security
默認提供的實現類不能知足需求的時候能夠擴展AuthenticationProvider
覆蓋supports(Class<?> authentication)
方法);spring
AuthenticationManager
接收 Authentication
對象做爲參數,並經過 authenticate(Authentication)
方法對其進行驗證;AuthenticationProvider
實現類用來支撐對 Authentication
對象的驗證動做;UsernamePasswordAuthenticationToken
實現了 Authentication
主要是將用戶輸入的用戶名和密碼進行封裝,並供給 AuthenticationManager
進行驗證;驗證完成之後將返回一個認證成功的 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
是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對應的Token copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw 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; }
ProviderManager
經過 AuthenticationProvider
擴展出更多的驗證提供的方式;而 AuthenticationProvider
自己也就是一個接口,從類圖中咱們能夠看出它的實現類AbstractUserDetailsAuthenticationProvider
和AbstractUserDetailsAuthenticationProvider
的子類DaoAuthenticationProvider
。DaoAuthenticationProvider
是Spring Security
中一個核心的Provider
,對全部的數據庫提供了基本方法和入口。app
DaoAuthenticationProvider
主要作了如下事情框架
#1.可直接返回BCryptPasswordEncoder,也能夠本身實現該接口使用本身的加密算法核心方法String encode(CharSequence rawPassword);和boolean matches(CharSequence rawPassword, String encodedPassword);
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
爲DaoAuthenticationProvider
提供了基本的認證方法;
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { #1.獲取用戶信息由子類實現即DaoAuthenticationProvider user = 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
並提供了相關的驗證邏輯;
UserDetails
AbstractUserDetailsAuthenticationProvider
定義了一個抽象的方法 protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
2. 三步驗證工做 1. preAuthenticationChecks 2. 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
是一個接口,提供了一個方法
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
該實現類主要是提供基於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 = ?";
該實現類主要是提供基於內存
對 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
和UserDetailsManage
r所構成;UserDetails
和UserDetailsManager
各司其責,一個是對基本用戶信息進行封裝,一個是對基本用戶信息進行管理;
特別注意
,UserDetailsService
、UserDetails
以及UserDetailsManager
都是可被用戶自定義的擴展點,咱們能夠繼承這些接口提供本身的讀取用戶來源和管理用戶的方法,好比咱們能夠本身實現一個 與特定 ORM 框架,好比 Mybatis 或者 Hibernate,相關的UserDetailsService
和UserDetailsManager
;