Springsecurity之認證過程簡析

    注:分析的Springsecurity版本是4.3.x,源碼可自行到github上去下載。

    先上一張圖,如下圖1.1所示:

      

                                                          圖1.1 Springsecurity認證的時序圖

1.AuthenticationManager

    AuthenticationManager的實現類ProviderManager的實現如下:

    List-1.1 ProviderManager的authenticate方法

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {
	private List<AuthenticationProvider> providers = Collections.emptyList();
	private AuthenticationManager parent;

	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();

		for (AuthenticationProvider provider : getProviders()) {
            //調用supports方法
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
                //調用provider的authenticate方法
				result = provider.authenticate(authentication);

				if (result != null) {
					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 {
                //如果result爲null且parent不爲null,那麼調用parent的authenticate方法
				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;
			}
		}

		}
...
  1. 遍歷AuthenticationProvider,調用provider的supports方法,如果返回爲true,那麼執行後面的流程。
  2. 之後調用AuthenticationProvider的authenticate方法,如果返回的result不爲null,那麼跳出循環。
  3. 如果遍歷完了AuthenticationProvider,result的值還是null且parent不爲null,那麼調用parent的authenticate方法。
  4. 如果得到的result值不爲null,那麼返回這個result給調用者。    

2.AuthenticationProvider

    AuthenticationProvider是個接口,如下List-2.1所示:

    List-2.1 AuthenticationProvider的接口定義

/**
 * Indicates a class can process a specific
 * {@link org.springframework.security.core.Authentication} implementation.
 *
 * @author Ben Alex
 */
public interface AuthenticationProvider {
	
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;

	boolean supports(Class<?> authentication);
}

以DaoAuthenticationProvider爲例,它的繼承圖如下所示:

                       

                                              圖2.1 DaoAuthenticationProvider的繼承圖

    AbstractUserDetailsAuthenticationProvider中的authenticate方法中,定義了主要的模板,即它裏面使用了Template方法。如下List-2.2所示:

    List-2.2 AbstractUserDetailsAuthenticationProvider的authenticate方法

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 {
                //retrieveUser方法是抽象的,它由子類來實現
				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 {
			preAuthenticationChecks.check(user);
            //additionalAuthenticationChecks方法是抽象的,由子類來實現。
			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;
			}
		}

		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

    retrieveUser和additionalAuthenticationChecks由子類DaoAuthenticationProvider來實現。DaoAuthenticationProvider中的retrieveUser方法,涉及UserDetailsService。

3.UserDetailsService

    它的實現如下List-3.1所示:

    List-3.1 UserDetailsService定義

public interface UserDetailsService {
	
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

    這個接口的有個實現類JdbcUserDetailsManager,它實現了接口UserDetailsManager,這個接口定義的是對用戶信息管理的接口。

 

參考:

  1. http://niocoder.com/2018/01/02/Spring-Security%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%80-Spring-Security%E8%AE%A4%E8%AF%81%E8%BF%87%E7%A8%8B/
  2. https://github.com/longfeizheng/logback
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章