1. 程式人生 > >SpringSecurity認證的原始碼解析

SpringSecurity認證的原始碼解析

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;
	}