Spring Security 源碼解析

《 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的核心類


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容