天天看點

Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

目錄

認證過程

AuthenticationManager

Authentication

AbstractAuthenticationToken

UsernamePasswordAuthenticationToken

 RememberMeAuthenticationToken

 AbstractOAuth2TokenAuthenticationToken

ProviderManager

AuthenticationProvider

DaoAuthenticationProvider

AbstractUserDetailsAuthenticationProvider 

UserDetails

UserDetailsChecker 

DaoAuthenticationProvider屬性

DaoAuthenticationProvider.retrieveUser()

 createSuccessAuthentication()

additionalAuthenticationChecks()

UserDetailsService

 JdbcUserDetailsManager

InMemoryUserDetailsManager

總結

引入

AuthenticationManagerBuilder

performBuild()

Filters

UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter

attemptAuthentication

AnonymousAuthenticationFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

AbstractSecurityInterceptor

AccessDecisionManager 

 AbstractAccessDecisionManager 

AccessDecisionVoter

 WebExpressionVoter

RoleVoter 

AuthenticatedVoter

AffirmativeBased

ConsensusBased

UnanimousBased

密碼驗證時序圖

Spring Security解決使用者認證(Authentication)和使用者授權(Authorization)2個問題。

源碼位址:https://github.com/spring-projects/spring-security/tree/5.2.1.RELEASE

認證過程

AuthenticationManager

該對象提供了認證方法的入口,接收一個

Authentiaton

對象作為參數;

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)			throws AuthenticationException;
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

Authentication

Authentication封裝了驗證請求資訊。

public interface Authentication extends Principal, Serializable {
	//擷取 授權資訊
	Collection<? extends GrantedAuthority> getAuthorities();

	//憑據
	Object getCredentials();

	//使用者資訊
	Object getDetails();

	//主體,可以了解驗證時的使用者名。
	Object getPrincipal();

	//是否被認證。
	boolean isAuthenticated();

	//認證結果設定。
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
           

 授權資訊:

public interface GrantedAuthority extends Serializable {
	String getAuthority();
}
           

 根據不同的認證類型,定義了多種實作:

Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

AbstractAuthenticationToken

private final Collection<GrantedAuthority> authorities;
	private Object details;
	private boolean authenticated = false;
           

UsernamePasswordAuthenticationToken

private final Object principal;
	private Object credentials;
           

 RememberMeAuthenticationToken

private final Object principal;
	private final int keyHash;
           

 AbstractOAuth2TokenAuthenticationToken

private Object principal;
	private Object credentials;
	private T token;
           

ProviderManager

它是 

AuthenticationManager

 的一個實作類,提供了基本的認證邏輯和方法;它包含了一個 

List<AuthenticationProvider>

 對象,通過 AuthenticationProvider 接口來擴充出不同的認證提供者(當

Spring Security

預設提供的實作類不能滿足需求的時候可以擴充

AuthenticationProvider

 覆寫

supports(Class<?> authentication)

方法);

private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
//驗證者 清單
	private List<AuthenticationProvider> providers = Collections.emptyList();
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private AuthenticationManager parent;
	private boolean eraseCredentialsAfterAuthentication = true;
           

AuthenticationManager

 通過 

authenticate(Authentication)

 方法對其進行驗證;

AuthenticationProvider

實作類用來支撐對 

Authentication

 對象的驗證動作;

UsernamePasswordAuthenticationToken

實作了 

Authentication

主要是将使用者輸入的使用者名和密碼進行封裝,并供給 

AuthenticationManager

 進行驗證;驗證完成以後将傳回一個認證成功的 

Authentication

 對象;

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

        //循環每個AuthenticationProvider 
		for (AuthenticationProvider provider : getProviders()) {
            //如果不支援Authentication,則繼續下一個
			if (!provider.supports(toTest)) {
				continue;
			}

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

			try {
                //驗證
				result = provider.authenticate(authentication);
                //如果驗證不為null,則表示驗證成功。
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}
        //如果所有provider都不支援驗證,則使用parent驗證。
		if (result == null && parent != null) {
			// Allow the parent to try.
			try {
				result = parentResult = 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 = parentException = e;
			}
		}
        //擦除敏感資訊。
		if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
				((CredentialsContainer) result).eraseCredentials();
			}

			// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
			if (parentResult == null) {
				eventPublisher.publishAuthenticationSuccess(result);
			}
			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}

		throw lastException;
	}
           

AuthenticationProvider

AuthenticationProvider提供使用者認證。

public interface AuthenticationProvider {
	Authentication authenticate(Authentication authentication)			throws AuthenticationException;
	boolean supports(Class<?> authentication);
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

 Spring Security提供了以下認證方式:

  • 使用者名,密碼
  • LDAP
  • 匿名方式
  • RememberMe
  • JWT
  • OAuth2
  • ... ...

DaoAuthenticationProvider

DaoAuthenticationProvider支援Authentication為UsernamePasswordAuthenticationToken的認證。

//AbstractUserDetailsAuthenticationProvider方法
    public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
	}
           

AbstractUserDetailsAuthenticationProvider 

AbstractUserDetailsAuthenticationProvider

DaoAuthenticationProvider

提供了基本的認證方法。

public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private UserCache userCache = new NullUserCache();
	private boolean forcePrincipalAsString = false;
	protected boolean hideUserNotFoundExceptions = true;
	private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
	private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
	private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
}
           

 認證過程如下:

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
//T0:
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> messages.getMessage(
						"AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));

//T1:
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();
//T2:
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
//T3:
		if (user == null) {
			cacheWasUsed = false;

			try {
//T4:
				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 {
//T5:
			preAuthenticationChecks.check(user);
//T6:
			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;
			}
		}
//T7:
		postAuthenticationChecks.check(user);

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

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
//T8:
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
           

T0:判斷是否是UsernamePasswordAuthenticationToken。

T1:擷取userName,預設:NONE_PROVIDED

T2:從緩存中擷取使用者資訊。

T3:如果緩存中沒有使用者資訊,則通過retrieveUser擷取(由子類實作)。

T4:通過retrieveUser擷取使用者資訊。

T5:驗證前check。

T6:附加check(由子類實作)。

T7:驗證

T8:驗證成功,建立Authentication。重新 new 了一個 

UsernamePasswordAuthenticationToken

,因為到這裡認證已經通過了,是以将 authorities 注入進去,并設定 authenticated 為 true,即不再需要認證。

protected abstract UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;

	protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException;
           

UserDetails

UserDetails封裝認證使用者的詳細資訊。

public interface UserDetails extends Serializable {
	//權限
	Collection<? extends GrantedAuthority> getAuthorities();
	String getPassword();
	String getUsername();
//是否賬戶過期
	boolean isAccountNonExpired();
//是否locked
	boolean isAccountNonLocked();
//憑據過期
	boolean isCredentialsNonExpired();
//賬戶是否可用
	boolean isEnabled();
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

UserDetailsChecker 

驗證使用者。

Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖
  • AccountStatusUserDetailsChecker

驗證使用者狀态。是否可用,過期,鎖定,憑據是否過期

  • DefaultPostAuthenticationChecks

驗證使用者狀态。憑據是否過期。

  • DefaultPreAuthenticationChecks

驗證使用者狀态。是否可用,過期,鎖定

DaoAuthenticationProvider屬性

private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

	private PasswordEncoder passwordEncoder;

	private volatile String userNotFoundEncodedPassword;

	private UserDetailsService userDetailsService;

	private UserDetailsPasswordService userDetailsPasswordService;
           

DaoAuthenticationProvider.retrieveUser()

protected final UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
//T1:UserDetailsService加載使用者資訊
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}
           

 createSuccessAuthentication()

驗證後對密碼進行加密。

@Override
	protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
		if (upgradeEncoding) {
			String presentedPassword = authentication.getCredentials().toString();
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}
           

additionalAuthenticationChecks()

通過使用者名,密碼進行驗證。

@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}

		String presentedPassword = authentication.getCredentials().toString();

		if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			logger.debug("Authentication failed: password does not match stored value");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}
	}
           

UserDetailsService

通過使用者名擷取使用者資訊。

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

 JdbcUserDetailsManager

基于JDBC通路UserDetail。預設需要包括以下表結構:

users (username, password, enabled)

authorities (username, authority) 

group_authorities (group_id, authority)

groups (id,group_name)

group_members (group_id, username)

InMemoryUserDetailsManager

基于記憶體通路UserDetails

總結

UserDetailsService

接口作為橋梁,是

DaoAuthenticationProvier

與特定使用者資訊來源進行解耦的地方,

UserDetailsService

UserDetails

UserDetailsManager

所構成;

UserDetails

UserDetailsManager

各司其責,一個是對基本使用者資訊進行封裝,一個是對基本使用者資訊進行管理;

特别注意

UserDetailsService

UserDetails

以及

UserDetailsManager

都是可被使用者自定義的擴充點,我們可以繼承這些接口提供自己的讀取使用者來源和管理使用者的方法,比如我們可以自己實作一個 與特定 ORM 架構,比如 Mybatis 或者 Hibernate,相關的

UserDetailsService

UserDetailsManager

引入

@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
	boolean debug() default false;
}
           
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
           
public class AuthenticationConfiguration {

	private ApplicationContext applicationContext;
	private AuthenticationManager authenticationManager;
	private boolean authenticationManagerInitialized;
	private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections
			.emptyList();
	private ObjectPostProcessor<Object> objectPostProcessor;
}
           

 AuthenticationManagerBuilder的實作類DefaultPasswordEncoderAuthenticationManagerBuilder 

@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(
			ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
		AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
		if (authenticationEventPublisher != null) {
			result.authenticationEventPublisher(authenticationEventPublisher);
		}
		return result;
	}
           

AuthenticationManagerBuilder

private AuthenticationManager parentAuthenticationManager;
	private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
	private UserDetailsService defaultUserDetailsService;
	private Boolean eraseCredentials;
	private AuthenticationEventPublisher eventPublisher;
           

performBuild()

構造ProviderManager

protected ProviderManager performBuild() throws Exception {
		if (!isConfigured()) {
			logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
			return null;
		}
		ProviderManager providerManager = new ProviderManager(authenticationProviders,
				parentAuthenticationManager);
		if (eraseCredentials != null) {
			providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
		}
		if (eventPublisher != null) {
			providerManager.setAuthenticationEventPublisher(eventPublisher);
		}
		providerManager = postProcess(providerManager);
		return providerManager;
	}
           

Filters

登入過程中會提供很多Filter用于處理登入請求。認證相關的主要包含後面描述的幾個

UsernamePasswordAuthenticationFilter

基于使用者名和密碼的認證Filter。

AbstractAuthenticationProcessingFilter

屬性

protected ApplicationEventPublisher eventPublisher;
	protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
//
	private AuthenticationManager authenticationManager;
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//RememberMeServices 
	private RememberMeServices rememberMeServices = new NullRememberMeServices();
//請求比對器
	private RequestMatcher requiresAuthenticationRequestMatcher;

	private boolean continueChainBeforeSuccessfulAuthentication = false;

	private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();

	private boolean allowSessionCreation = true;
//認證成功handler
	private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
//認證失敗handler
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
           

doFilter()

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
//如果請求不比對Filter,則使用原始chain。
		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);
//結果為null,表示認證失敗。
			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;
		}

		// 如果允許驗證繼續其他認真。
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
        //認證成功,回調
		successfulAuthentication(request, response, chain, authResult);
	}
           

 真實驗證由子類實作。

public abstract Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException, IOException,
			ServletException;
           

驗證成功或失敗回調

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回調
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}



protected void unsuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException failed)
			throws IOException, ServletException {
		SecurityContextHolder.clearContext();

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication request failed: " + failed.toString(), failed);
			logger.debug("Updated SecurityContextHolder to contain null Authentication");
			logger.debug("Delegating to authentication failure handler " + failureHandler);
		}

		rememberMeServices.loginFail(request, response);

		failureHandler.onAuthenticationFailure(request, response, failed);
	}
           

attemptAuthentication

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
//必須是POST方法
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
//擷取使用者名和密碼
		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();
//封裝請求
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
//調用AuthenticationManager()驗證
		return this.getAuthenticationManager().authenticate(authRequest);
	}
           

AnonymousAuthenticationFilter

AnonymousAuthenticationFilter

過濾器是在

UsernamePasswordAuthenticationFilter

等過濾器之後,如果它前面的過濾器都沒有認證成功,

Spring Security

則為目前的

SecurityContextHolder

中添加一個

Authenticaiton

 的匿名實作類

AnonymousAuthenticationToken

;

public AnonymousAuthenticationFilter(String key) {
		this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
	}
           

doFilter方法,直接觸發原始chain過濾。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			SecurityContextHolder.getContext().setAuthentication(
					createAuthentication((HttpServletRequest) req));
... ... 
		}
		else {
			... ... 
			}
		}

		chain.doFilter(req, res);
	}

//設定匿名token
protected Authentication createAuthentication(HttpServletRequest request) {
		AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
				principal, authorities);
		auth.setDetails(authenticationDetailsSource.buildDetails(request));

		return auth;
	}
           

ExceptionTranslationFilter

ExceptionTranslationFilter

 異常處理過濾器,該過濾器用來處理在系統認證授權過程中抛出的異常(也就是下一個過濾器

FilterSecurityInterceptor

),主要是 處理 

AuthenticationException

 和 

AccessDeniedException

 。

FilterSecurityInterceptor

此過濾器為認證授權過濾器鍊中最後一個過濾器,該過濾器之後就是請求真正的處理邏輯。

public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}


public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
            //已經處理。
            //直接調用真實服務。
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			// 第一次調用,處理checking。
			if (fi.getRequest() != null && observeOncePerRequest) {
            //設定為true。
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}
            //前置處理
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
            //真實業務邏輯。
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}
            //後置處理。
			super.afterInvocation(token, null);
		}
	}
           

AbstractSecurityInterceptor

protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private ApplicationEventPublisher eventPublisher;
	private AccessDecisionManager accessDecisionManager;
	private AfterInvocationManager afterInvocationManager;
	private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();
	private RunAsManager runAsManager = new NullRunAsManager();
           

beforeInvocation

//object:請求的url。
protected InterceptorStatusToken beforeInvocation(Object object) {
		Assert.notNull(object, "Object was null");
		final boolean debug = logger.isDebugEnabled();

		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(
					"Security invocation attempted for object "
							+ object.getClass().getName()
							+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
							+ getSecureObjectClass());
		}
//使用目前的通路資源路徑去比對自定義的比對規則。
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
//如果沒有,則傳回null。
		if (attributes == null || attributes.isEmpty()) {
			if (rejectPublicInvocations) {
				throw new IllegalArgumentException(
						"Secure object invocation "
								+ object
								+ " was denied as public invocations are not allowed via this interceptor. "
								+ "This indicates a configuration error because the "
								+ "rejectPublicInvocations property is set to 'true'");
			}

			if (debug) {
				logger.debug("Public object - authentication not attempted");
			}

			publishEvent(new PublicInvocationEvent(object));

			return null; // no further work post-invocation
		}

		if (debug) {
			logger.debug("Secure object: " + object + "; Attributes: " + attributes);
		}
//沒有驗證
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}
//驗證過的資訊。
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
//鑒權。
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}

		if (debug) {
			logger.debug("Authorization successful");
		}

		if (publishAuthorizationSuccess) {
			publishEvent(new AuthorizedEvent(object, attributes, authenticated));
		}

		// Attempt to run as a different user
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
				attributes);

		if (runAs == null) {
			if (debug) {
				logger.debug("RunAsManager did not change Authentication object");
			}

			// no further work post-invocation
			return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
					attributes, object);
		}
		else {
			if (debug) {
				logger.debug("Switching to RunAs Authentication: " + runAs);
			}

			SecurityContext origCtx = SecurityContextHolder.getContext();
			SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
			SecurityContextHolder.getContext().setAuthentication(runAs);

			// need to revert to token.Authenticated post-invocation
			return new InterceptorStatusToken(origCtx, true, attributes, object);
		}
	}
           
protected void finallyInvocation(InterceptorStatusToken token) {
		if (token != null && token.isContextHolderRefreshRequired()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Reverting to original Authentication: "
						+ token.getSecurityContext().getAuthentication());
			}

			SecurityContextHolder.setContext(token.getSecurityContext());
		}
	}
           

AccessDecisionManager 

AccessDecisionManager 用于鑒權。

public interface AccessDecisionManager {
	
	void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

 AbstractAccessDecisionManager 

public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
		InitializingBean, MessageSourceAware {
	private List<AccessDecisionVoter<?>> decisionVoters;

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private boolean allowIfAllAbstainDecisions = false;
}
           
public boolean supports(ConfigAttribute attribute) {
		for (AccessDecisionVoter voter : this.decisionVoters) {
			if (voter.supports(attribute)) {
				return true;
			}
		}

		return false;
	}


	public boolean supports(Class<?> clazz) {
		for (AccessDecisionVoter voter : this.decisionVoters) {
			if (!voter.supports(clazz)) {
				return false;
			}
		}

		return true;
	}
           

AccessDecisionVoter

投票。即權限判斷。

public interface AccessDecisionVoter<S> {
    //投票結果:
	int ACCESS_GRANTED = 1;//通過。
	int ACCESS_ABSTAIN = 0;//棄權。
	int ACCESS_DENIED = -1;//反對

	boolean supports(ConfigAttribute attribute);

	boolean supports(Class<?> clazz);
	
	int vote(Authentication authentication, S object,			Collection<ConfigAttribute> attributes);
}
           
Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

 WebExpressionVoter

WebExpressionVoter ,它能了解怎樣解析 SpEL 表達式。 WebExpressionVoter 借助于WebSecurityExpressionHandler 接口的一個實作類來達到這個目的。

public int vote(Authentication authentication, FilterInvocation fi,
			Collection<ConfigAttribute> attributes) {
		assert authentication != null;
		assert fi != null;
		assert attributes != null;

		WebExpressionConfigAttribute weca = findConfigAttribute(attributes);

		if (weca == null) {
			return ACCESS_ABSTAIN;
		}

		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
				fi);
		ctx = weca.postProcess(ctx, fi);

		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
				: ACCESS_DENIED;
	}
           

RoleVoter 

基于角色的鑒權,權限以ROLE_開頭。

AuthenticatedVoter

根據認證結果鑒權。RememberMe會使用到。

public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";
	public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";
	public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";
           

AffirmativeBased

AccessDecisionManager的預設實作。隻要有一個通過則通過。沒有反對的也通過。

public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int deny = 0;

		for (AccessDecisionVoter voter : getDecisionVoters()) {
			int result = voter.vote(authentication, object, configAttributes);

			if (logger.isDebugEnabled()) {
				logger.debug("Voter: " + voter + ", returned: " + result);
			}

			switch (result) {
			case AccessDecisionVoter.ACCESS_GRANTED:
				return;

			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;

				break;

			default:
				break;
			}
		}

		if (deny > 0) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}

		// To get this far, every AccessDecisionVoter abstained
		checkAllowIfAllAbstainDecisions();
	}
           

ConsensusBased

同意大于反對,或者同意等于反對(如果允許)則通過。

UnanimousBased

隻要有一個不通過,就拒絕。

密碼驗證時序圖

Spring Security源碼解析(一)——認證和鑒權認證過程引入Filters密碼驗證時序圖

繼續閱讀