SpringSecurity認證的源碼解析
認證處理流程
認證過程涉及到的類
在UsernamePasswordAuthenticationFilter類的處理
1.首先經過的是一個UsernamePasswordAuthenticationFilter過濾器,該過濾器在獲取到用戶名和密碼之後就去構建一個 UsernamePasswordAuthenticationToken的對象,該對象是Authentication(即用戶的認證信息) UsernamePasswordAuthenticationFilter.java
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
UsernamePasswordAuthenticationToken.java
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
// 這個地方調用父類的該AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities),但是該方法需要傳遞一組權限進來,在驗證玩之前還是沒有任何權限信息的,所以傳遞一個空進來。
super((Collection)null);
//對應用戶名
this.principal = principal;
//對應密碼
this.credentials = credentials;
//表示信息沒經過任何認證,所以是false
this.setAuthenticated(false);
}
UsernamePasswordAuthenticationFilter.java
//將請求的信息放入上面生成的UsernamePasswordAuthenticationToken裏面
this.setDetails(request, authRequest);
在AuthenticationManager裏面處理的流程
直接調用了它的子類(ProviderManager)的authenticate方法return this.getAuthenticationManager().authenticate(authRequest);
ProviderManager的處理流程
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
//遍歷是否支持當前的校驗方式
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);
if (result != null) {
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;
}
}
}
if (result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
this.eventPublisher.publishAuthenticationSuccess(result);
return result;
} else {
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;
}
}
這段代碼遍歷循環判斷是否支持該認證方式。
result = provider.authenticate(authentication);
在AbstractUserDetailsAuthenticationProvider.java裏面
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//獲取到用戶的信息(UserDetails)
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
result = provider.authenticate(authentication)調用的實際上是調用DaoAuthenticationProvider的authenticate(authentication),該方法裏面調用了 loadedUser = this.getUserDetailsService().loadUserByUsername(username);實際上是調用我們自己寫的UserDetailService的實現類。這個時候就獲取到了UserDetail對象了: MyDetailService.java
@Component//TODO 使之成爲用戶的bean
public class MyDetailService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
Logger logger = LoggerFactory.getLogger(MyDetailService.class);
[@Override](https://my.oschina.net/u/1162528)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//TODO 根據用戶名查找用戶信息
logger.info("登陸用戶名:"+s);
String encode = passwordEncoder.encode("123456");
logger.info("登陸用戶名:"+encode);
return new User(s,encode,true,true,true,true, AuthorityUtils.createAuthorityList("admin"));
}
}
AbstractUserDetailsAuthenticationProvider.java裏面進行前置,附加,後置檢查。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
//預檢查
this.preAuthenticationChecks.check(user);
//附加檢查
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
//後置檢查
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//成功創建
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
this.createSuccessAuthentication(principalToReturn, authentication, user)方法如下:
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
該方法從新創建了一個UsernamePasswordAuthenticationToken對象,該對象已經帶有認證的信息。
此時調用的構造函數如下
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
//此時具備了權限了信息
super(authorities);
this.principal = principal;
this.credentials = credentials;
//此時已經校驗通過了。
super.setAuthenticated(true);
}
到這裏已經完成認證獲取到了我們需要的Authentication實例對象,需要沿着圖返回去。
在AbstractAuthenticationProcessingFilter.java的public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)裏面調用了successfulAuthentication(request, response, chain, authResult);這個方法就是調用我們自己寫的成功處理器。如果期間出錯就會調用我們自己創建的失敗處理器。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//調用我們自己的處理器
successHandler.onAuthenticationSuccess(request, response, authResult);
}
認證結果在多個請求之間共享
認證請求緩存的過程
認證流程中SecurityPersistenceFilter的位置
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//將認證放入了SecurityContext類中然後將SecurityContext放入SecurityContextHolder裏面。
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
SecurityContext類實際上是Authentication的包裝。
SecurityContextHolder實際上是ThreadLocal的實現,使得認證的信息在線程的其他方法都可以獲取。
SecurityContextPersistenceFilter進來的時候檢查session是否有認證信息,有就放入線程,後面的類,方法都可以使用,沒有就直接進入後的過濾器進行校驗。響應的時候就檢查線程是否有有驗證信息,有就放入session
獲取用戶信息
@GetMapping("getAuthentication")
public Authentication getAuthentication(){
return SecurityContextHolder.getContext().getAuthentication();
}
@GetMapping("getAuthentication2")
public Object getAuthentication2(@AuthenticationPrincipal UserDetails userDetails){
return userDetails;
}