《 Spring Security 源碼解析 (一)》
1 簡介
? 最近一個項目,使用了試下流行的Spring Cloud 微服務(wù)架構(gòu),使用Spring Cloud oauth2.0 協(xié)議搭建訪問授權(quán)器。由于項目體量較小,不對外提供授權(quán)服務(wù),故采用oauth協(xié)議的密碼模式來做令牌授權(quán)。項目中深深體會到了spring Security的強(qiáng)大,寫下此篇博客來記錄所學(xué)到的知識,希望對大家建立spring Security的體系能產(chǎn)生幫助。鑒于本人水平所限,有錯誤不足之處,望各位指正。
?spring security 核心組件
2.1 SecurityContextPersistenceFilter
spring 使用過濾鏈對url進(jìn)行攔截,在過濾鏈的頂端是SecurityContextPersistenceFilter,這個過濾器的作用:用戶在登錄過后,后續(xù)的訪問通過sessionid來識別,當(dāng)如今微服務(wù)流行時,前后端傳送是不傳輸seesion的,但過濾器還是可以幫我們清除上下文的一些信息。具體的代碼:
//執(zhí)行過濾鏈
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//獲取請求和響應(yīng)
? ? ? ? HttpServletRequest request = (HttpServletRequest)req;
? ? ? ? HttpServletResponse response = (HttpServletResponse)res;
? ? ? ? if (request.getAttribute("__spring_security_scpf_applied") != null) {
? ? ? ? ? ? chain.doFilter(request, response);
? ? ? ? } else {
? ? ? ? ? ? boolean debug = this.logger.isDebugEnabled();
? ? ? ? ? ? request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
? ? ? ? ? ? if (this.forceEagerSessionCreation) {
? ? ? ? ? ? ? ? HttpSession session = request.getSession();
? ? ? ? ? ? ? ? if (debug && session.isNew()) {
? ? ? ? ? ? ? ? ? ? this.logger.debug("Eagerly created session: " + session.getId());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
//包裝request 和response
? ? ? ? ? ? HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
? ? ? ? ? ? //從安全上下文長褲中獲取SecurityContext容器
? ? ? ? ? ? SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
? ? ? ? ? ? boolean var13 = false;
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? var13 = true;
? ? ? ? ? ? ? ? // 獲取安全響應(yīng)上下文
? ? ? ? ? ? ? ? SecurityContextHolder.setContext(contextBeforeChainExecution);
? ? ? ? ? ? ? ? chain.doFilter(holder.getRequest(), holder.getResponse());
? ? ? ? ? ? ? ? var13 = false;
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? if (var13) {
? ? ? ? ? ? ? ? //最終清除安全上下文信息
? ? ? ? ? ? ? ? ? ? SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
? ? ? ? ? ? ? ? ? ? SecurityContextHolder.clearContext();
? ? ? ? ? ? ? ? ? ? this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
? ? ? ? ? ? ? ? ? ? request.removeAttribute("__spring_security_scpf_applied");
? ? ? ? ? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? ? ? ? ? this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
? ? ? ? ? ? SecurityContextHolder.clearContext();
? ? ? ? ? ? this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
? ? ? ? ? ? request.removeAttribute("__spring_security_scpf_applied");
? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
? ? ? ? ? ? }
? ? ? ? }
? ? }
從源碼中可以看出安全上下文的信息是存在SecurityContextHolder這個容器當(dāng)中的,當(dāng)服務(wù)請求結(jié)束的時候,SecurityContextHolder清除了session信息。過濾器一般負(fù)責(zé)核心處理,具體業(yè)務(wù),交給其他類實現(xiàn),在spring的設(shè)計中我們可以看到很多諸如此類的設(shè)計除了SecurityContextPersistenceFilter,在spring Security的一堆過濾器鏈中,在學(xué)習(xí)中我們應(yīng)該舉一反三,這樣有利于我們對源碼的學(xué)習(xí),也能提高自己的代碼能力。還有一些很重要的filter,比如AbstractAuthenticationProcessingFilter,以及它的子類SecurityContextPersistenceFilter等等都是很總要的過濾器,我們將重點對AbstractAuthenticationProcessingFilter進(jìn)行講解
2.2 SecurityContextHolder
SecurityContextHolder用于存儲安全上下文(SecurityContext)信息。當(dāng)前用戶信息都被保存到SecurityContextHolder中,其實是保存到TContextHolderStrategy的實現(xiàn)類中,顧名思義,SecurityContextHolder綁定了本地線程,在用戶退出后, SecurityContextPersistenceFilter會qing清除Context信息。我們來看源碼:
public class SecurityContextHolder {
//存儲策略
? ? public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
? ? public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
? ? public static final String MODE_GLOBAL = "MODE_GLOBAL";
? ? public static final String SYSTEM_PROPERTY = "spring.security.strategy";
? ? private static String strategyName = System.getProperty("spring.security.strategy");
? ? private static SecurityContextHolderStrategy strategy;
? ? private static int initializeCount = 0;
? ? public SecurityContextHolder() {
? ? }
//清除上下文
? ? public static void clearContext() {
? ? ? ? strategy.clearContext();
? ? }
? ? private static void initialize() {
? ? ? ? if (!StringUtils.hasText(strategyName)) {
? ? ? ? ? ? strategyName = "MODE_THREADLOCAL";
? ? ? ? }
? ? ? ? if (strategyName.equals("MODE_THREADLOCAL")) {
? ? ? ? //創(chuàng)建真正的安全上下文容器
? ? ? ? ? ? strategy = new ThreadLocalSecurityContextHolderStrategy();
? ? ? ? } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
? ? ? ? ? ? strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
? ? ? ? } else if (strategyName.equals("MODE_GLOBAL")) {
? ? ? ? ? ? strategy = new GlobalSecurityContextHolderStrategy();
? ? ? ? } else {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? Class<?> clazz = Class.forName(strategyName);
? ? ? ? ? ? ? ? Constructor<?> customStrategy = clazz.getConstructor();
? ? ? ? ? ? ? ? strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
? ? ? ? ? ? } catch (Exception var2) {
? ? ? ? ? ? ? ? ReflectionUtils.handleReflectionException(var2);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? ++initializeCount;
? ? }
//....代碼省略
//? 初始化
? ? static {
? ? ? ? initialize();
? ? }
}
從代碼中我們也可以看出,SecurityContextHolder就是這么和線程關(guān)聯(lián)的,并提供了clear的方法,方便進(jìn)程結(jié)束后對安全上線文的清除。寫在這里我們也可以仿照SecurityContextHolder的策略來實現(xiàn)我們的業(yè)務(wù)邏輯:
@Slf4j
@Service
@Lazy(false)
public class TenantContextHolder implements ApplicationContextAware, DisposableBean {
private static ThreadLocal<String> tenantThreadLocal= new ThreadLocal<>();
private static ApplicationContext applicationContext =null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
TenantContextHolder.applicationContext =applicationContext;
}
public static final void setTenant(String schema){
tenantThreadLocal.set(schema);
}
public static final String getTenant(){
String schema = tenantThreadLocal.get();
if(schema == null){
schema = "";
}
return schema;
}
@Override
public void destroy() throws Exception {
TenantContextHolder.clearHolder();
}
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除TenantContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
}
2.3 AbstractAuthenticationProcessingFilter
從名字也可以看出這是一個抽象類,這是一個很重要的類,用戶權(quán)限校驗都與此類有關(guān),請求進(jìn)入filter會執(zhí)行doFilter()方法,首先判斷是否能夠處理當(dāng)前請求,如果是則調(diào)用子類的attemptAuthentication()方法進(jìn)行驗證,在這個抽象類中,引入了統(tǒng)一認(rèn)證管理AuthenticationManager ,登錄成功AuthenticationSuccessHandler等等,登錄成功后,又定義了登錄成功后的方法,將封裝的Authentication信息封裝到SecurityContextHolder中,這個類中業(yè)務(wù)邏輯很多,我這里代碼沒有貼全,希望朋友們自己能夠閱讀源碼,了解這個類的核心。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
//事件發(fā)布器
? ? protected ApplicationEventPublisher eventPublisher;
//核心幾口 所有的權(quán)限校驗都由它進(jìn)行統(tǒng)一管理
? ? private AuthenticationManager authenticationManager;
//記住我
? ? private RememberMeServices rememberMeServices = new NullRememberMeServices();
? ? private RequestMatcher requiresAuthenticationRequestMatcher;
? ? private boolean continueChainBeforeSuccessfulAuthentication = false;
? ? private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
? ? //登錄成功處理
? ? private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
? ? //失敗處理
? ? private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
? //? 代碼省略.........
? ? public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
? ? ? ? HttpServletRequest request = (HttpServletRequest)req;
? ? ? ? HttpServletResponse response = (HttpServletResponse)res;
? ? ? ? //判斷當(dāng)前的filter是否可以處理當(dāng)前請求,不可以的話則交給下一個filter處理
? ? ? ? if (!this.requiresAuthentication(request, response)) {
? ? ? ? ? ? chain.doFilter(request, response);
? ? ? ? } else {
? ? ? ? ? ? if (this.logger.isDebugEnabled()) {
? ? ? ? ? ? ? ? this.logger.debug("Request is to process authentication");
? ? ? ? ? ? }
? ? ? ? ? ? Authentication authResult;
? ? ? ? ? ? try {
? ? ? ? ? ? //對權(quán)限進(jìn)行校驗
? ? ? ? ? ? ? ? authResult = this.attemptAuthentication(request, response);
? ? ? ? ? ? ? ? if (authResult == null) {
? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
//認(rèn)證成功
? ? ? ? ? ? this.sessionStrategy.onAuthentication(authResult, request, response);
? ? ? ? ? ? if (this.continueChainBeforeSuccessfulAuthentication) {
? ? ? ? ? ? ? ? chain.doFilter(request, response);
? ? ? ? ? ? }
? ? ? ? ? ? this.successfulAuthentication(request, response, chain, authResult);
? ? ? ? }
? ? }
//權(quán)限校驗的方法
? ? public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;
? ? public void setAuthenticationManager(AuthenticationManager authenticationManager) {
? ? ? ? this.authenticationManager = authenticationManager;
? ? }
}
AbstractAuthenticaitonProcessingFilter有很4個子類,比如:UsernamepasswordProcessingFiter,OAuth2ClientAuthenticationFilter,他們都實現(xiàn)了**attemptAuthentication**方法,我們來看:
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
? ? public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
? ? public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
? ? private String usernameParameter = "username";
? ? private String passwordParameter = "password";
? ? private boolean postOnly = true;
//在webconfig里,配置http.login 并且路徑為/login、方法為post 就會被這個攔截器攔截
? ? public UsernamePasswordAuthenticationFilter() {
? ? ? ? super(new AntPathRequestMatcher("/login", "POST"));
? ? }
? ? public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
? ? ? ? if (this.postOnly && !request.getMethod().equals("POST")) {
? ? ? ? ? ? throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
? ? ? ? } else {
? ? ? ? ? ? String username = this.obtainUsername(request);
? ? ? ? ? ? String password = this.obtainPassword(request);
? ? ? ? ? ? if (username == null) {
? ? ? ? ? ? ? ? username = "";
? ? ? ? ? ? }
? ? ? ? ? ? if (password == null) {
? ? ? ? ? ? ? ? password = "";
? ? ? ? ? ? }
? ? ? ? ? ? // 注意 劃重點
//獲取用戶名和密碼 并將用戶名和密碼封裝成一個UsernamePasswordAuthenticationToken這么個token
? ? ? ? ? ? username = username.trim();
? ? ? ? ? ? UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
? ? ? ? ? ? this.setDetails(request, authRequest);
? ? ? ? ? ? //將校驗交由AuthenticationManger的authenticate方法去執(zhí)行
? ? ? ? ? ? return this.getAuthenticationManager().authenticate(authRequest);
? ? ? ? }
? ? }
2.4 AuthenticationManager
上文我們提到,所有的權(quán)限校驗都是由AuthenticationManager去完成的 那么我們?nèi)タ纯此脑创a
public interface AuthenticationManager {
? ? Authentication authenticate(Authentication var1) throws AuthenticationException;
}
我們看到AuthenticationManager是一個接口,所有的校驗都是由他的實現(xiàn)類去完成的,AuthenticationManager制作統(tǒng)一的管理,這里我們就要開動腦筋想想,這么設(shè)計的用意是什么,這里我留著空間讓大家思考,廢話不多說我們來看看他的兩個核心實現(xiàn)類OAuth2AuthenticationManager、ProviderManager ,每個都為我們提供了校驗接入點,接下來我們就說說ProviderManager,OAuth2AuthenticationManager則放到Oauth相關(guān)章節(jié)去講
2.5 ProviderManager
我們知道,用戶校驗的話有很多種,比如說用戶名+密碼、手機(jī)號+驗證碼、郵箱+密碼、social登錄等等,那么ProviderManager是怎么知道我們是哪一種請求呢?我們繼續(xù)來看看源碼:
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
// 代碼省略 ....
? ? public ProviderManager(List<AuthenticationProvider> providers) {
? ? ? ? this(providers, (AuthenticationManager)null);
? ? }
? ? public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
? ? ? ? this.eventPublisher = new ProviderManager.NullEventPublisher();
? ? ? ? this.providers = Collections.emptyList();
? ? ? ? this.messages = SpringSecurityMessageSource.getAccessor();
? ? ? ? this.eraseCredentialsAfterAuthentication = true;
? ? ? ? Assert.notNull(providers, "providers list cannot be null");
? ? ? ? this.providers = providers;
? ? ? ? this.parent = parent;
? ? ? ? this.checkState();
? ? }
//權(quán)限校驗
? ? public Authentication authenticate(Authentication authentication) throws AuthenticationException {
? ? //獲取當(dāng)前認(rèn)證的類型
? ? ? ? Class<? extends Authentication> toTest = authentication.getClass();
? ? ? ? AuthenticationException lastException = null;
? ? ? ? Authentication result = null;
? ? ? ? boolean debug = logger.isDebugEnabled();
? ? ? ? //獲取迭代器
? ? ? ? Iterator var6 = this.getProviders().iterator();
//循環(huán)獲取 這里和4.x版本有所不同個 有興趣的可以看看4.x的版本
? ? ? ? while(var6.hasNext()) {
? ? ? ? ? ? AuthenticationProvider provider = (AuthenticationProvider)var6.next();
? ? ? ? ? ? if (provider.supports(toTest)) {
? ? ? ? ? ? ? ? if (debug) {
? ? ? ? ? ? ? ? ? ? logger.debug("Authentication attempt using " + provider.getClass().getName());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? result = provider.authenticate(authentication);
? ? ? ? ? ? ? ? ? ? //通過當(dāng)前類型獲取到認(rèn)證信息且不為null 則停止循環(huán)
? ? ? ? ? ? ? ? ? ? if (result != null) {
? ? ? ? ? ? ? ? ? ? 將result裝換成Authentication信息
? ? ? ? ? ? ? ? ? ? ? ? this.copyDetails(authentication, result);
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? } catch (AccountStatusException var11) {
? ? ? ? ? ? ? ? ? ? this.prepareException(var11, authentication);
? ? ? ? ? ? ? ? ? ? throw var11;
? ? ? ? ? ? ? ? } catch (InternalAuthenticationServiceException var12) {
? ? ? ? ? ? ? ? ? ? this.prepareException(var12, authentication);
? ? ? ? ? ? ? ? ? ? throw var12;
? ? ? ? ? ? ? ? } catch (AuthenticationException var13) {
? ? ? ? ? ? ? ? ? ? lastException = var13;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
//如果結(jié)果為空 則調(diào)用父類的authenticate方法 這里可以理解成遞歸
? ? ? ? if (result == null && this.parent != null) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? result = this.parent.authenticate(authentication);
? ? ? ? ? ? } catch (ProviderNotFoundException var9) {
? ? ? ? ? ? ? ? ;
? ? ? ? ? ? } catch (AuthenticationException var10) {
? ? ? ? ? ? ? ? lastException = var10;
? ? ? ? ? ? }
? ? ? ? }
//獲取到結(jié)果
? ? ? ? if (result != null) {
? ? ? ? ? ? if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
? ? ? ? ? ? ? //移除密碼
? ? ? ? ? ? ? ((CredentialsContainer)result).eraseCredentials();
? ? ? ? ? ? }
//發(fā)布驗證成功事件 并返回結(jié)果
? ? ? ? ? ? this.eventPublisher.publishAuthenticationSuccess(result);
? ? ? ? ? ? return result;
? ? ? ? } else {
? ? ? ? //執(zhí)行到此,說明沒有認(rèn)證成功,包裝異常信息
? ? ? ? ? ? if (lastException == null) {
? ? ? ? ? ? ? ? lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
? ? ? ? ? ? }
? ? ? ? ? ? this.prepareException((AuthenticationException)lastException, authentication);
? ? ? ? ? ? throw lastException;
? ? ? ? }
? ? }
? ? public List<AuthenticationProvider> getProviders() {
? ? ? ? return this.providers;
? ? }
// .................
}
看到這里的話,我想剛才留的思考就可以解答了,當(dāng)獲取的認(rèn)證信息和Providers不相符的時候,就調(diào)用父類AuthenticationManger的authenticate方法 ,與下個provider進(jìn)行匹配,通俗來說,當(dāng)前臺是已郵箱加密碼登錄的,首先 Iterator 獲取的可能是usernamepassword模式的,那么查出結(jié)果是空,就調(diào)用父類的方法,又走了一遍子類實現(xiàn),又繼續(xù)認(rèn)證,直到認(rèn)證成功,將返回的 result 既 Authentication 對象進(jìn)一步封裝為 Authentication Token,比如usernamepasswordtoken 、remembermetoken等等。
2.6 Authentication
上文我們一直有提到過Authentication ,那么我們來看一下Authentication具體是什么
public interface Authentication extends Principal, Serializable {
//權(quán)限信息集合
? ? Collection<? extends GrantedAuthority> getAuthorities();
//獲取憑證
? ? Object getCredentials();
//獲取詳情
? ? Object getDetails();
//獲取當(dāng)前用戶
? ? Object getPrincipal();
//是否認(rèn)證
? ? boolean isAuthenticated();
? ? void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
認(rèn)證授權(quán)信息都會封裝到這里,比如我們用戶登錄后 將用戶名和密碼封裝成UsernamePasswordToken 就是實現(xiàn)了這個接口,它封裝了用戶權(quán)限信息,以及對象信息,是整個框架的核心類
2.7 UserDetailsService和 UserDetails
這也是Spring Security 最重要的接口,通過該接口的loadUserByUsername()方法返回一個UserDetails對象,那么這個對象是什么呢?
public interface UserDetailsService {
? ? UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
我們看到UserDetailsService就定義了一個接口,那么想必它也有很多適用不同業(yè)務(wù)功能的實現(xiàn)類,我們稍后再看,先瞧瞧UserDetails這個類
public interface UserDetails extends Serializable {
//權(quán)限集合
? ? Collection<? extends GrantedAuthority> getAuthorities();
? ? String getPassword();
? ? String getUsername();
//是否過期
? ? boolean isAccountNonExpired();
//是被鎖
? ? boolean isAccountNonLocked();
//憑證(密碼)是否失效
? ? boolean isCredentialsNonExpired();
//用戶是否可用(是否刪除)
? ? boolean isEnabled();
}
是不是和Authentication很像呢!那為什么要定義兩個呢?這里依舊留一個疑問。我們再來看UserDetails,還是個接口,那么我們來看他的子類實現(xiàn)
public class User implements UserDetails, CredentialsContainer {
? ? private static final long serialVersionUID = 500L;
? ? private static final Log logger = LogFactory.getLog(User.class);
? ? private String password;
? ? private final String username;
? ? private final Set<GrantedAuthority> authorities;
? ? private final boolean accountNonExpired;
? ? private final boolean accountNonLocked;
? ? private final boolean credentialsNonExpired;
? ? private final boolean enabled;
? ? public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
? ? ? ? this(username, password, true, true, true, true, authorities);
? ? }
? ? public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
? ? ? ? if (username != null && !"".equals(username) && password != null) {
? ? ? ? ? ? this.username = username;
? ? ? ? ? ? this.password = password;
? ? ? ? ? ? this.enabled = enabled;
? ? ? ? ? ? this.accountNonExpired = accountNonExpired;
? ? ? ? ? ? this.credentialsNonExpired = credentialsNonExpired;
? ? ? ? ? ? this.accountNonLocked = accountNonLocked;
? ? ? ? ? ? this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
? ? ? ? } else {
? ? ? ? ? ? throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
? ? ? ? }
? ? }
// 代碼省略.............
}
當(dāng)我們使用用戶名密碼登錄的時候,從數(shù)據(jù)庫中查到的用戶名和密碼都會封裝到這里
2.8DaoAuthenticationProvider
用戶登錄后將用戶登錄提交信息封裝成了Authentication對象,那么我們?nèi)绾螐臄?shù)據(jù)庫中獲取用戶名和密碼來進(jìn)行校驗,如何進(jìn)行密碼加解密。這里我們就要說說DaoAuthenticationProvider了:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
? ? private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
? ? //問價加解密處理
? ? private PasswordEncoder passwordEncoder;
? ? private volatile String userNotFoundEncodedPassword;
? ? //注入UserDetailsService 調(diào)用子類的loadUserByUsername方法 獲得UserDetails對象
? ? private UserDetailsService userDetailsService;
? ? public DaoAuthenticationProvider() {
? ? //密碼處理
? ? ? ? this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
? ? }
//仔細(xì)看好這里? 從數(shù)據(jù)庫中獲取到的UserDetails對象和前臺表單提交封裝的UsernamePasswordAuthenticationToken對象 進(jìn)行比較
? ? protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
? ? ? ? if (authentication.getCredentials() == null) {
? ? ? ? ? ? this.logger.debug("Authentication failed: no credentials provided");
? ? ? ? ? ? throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
? ? ? ? } else {
? ? ? ? ? ? String presentedPassword = authentication.getCredentials().toString();
? ? ? ? ? ? if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
? ? ? ? ? ? ? ? this.logger.debug("Authentication failed: password does not match stored value");
? ? ? ? ? ? ? ? throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? protected void doAfterPropertiesSet() throws Exception {
? ? ? ? Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
? ? }
? ? protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
? ? ? ? this.prepareTimingAttackProtection();
? ? ? ? try {
? ? ? ? //在這里通過用戶名從數(shù)據(jù)庫中拿到UserDetails 然后交給additionalAuthenticationChecks去驗證
? ? ? ? ? ? UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
? ? ? ? ? ? if (loadedUser == null) {
? ? ? ? ? ? ? ? throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return loadedUser;
? ? ? ? ? ? }
? ? // ..........................
}
我們以表單登錄的來看一下用戶登錄的執(zhí)行流程

圖片來源于網(wǎng)絡(luò),侵權(quán)請留言??!
讓我們再梳理一下Spring security的核心類
