Анализ процесса аутентификации Spring Security и связанных с ним фильтров

задняя часть Spring исходный код Безопасность

Оригинальный адрес блога:блог Пимайка

предисловие

предыдущий постАнализ основных компонентов Spring SecurityВ этой статье представлены основные компоненты Spring Security.На основе предыдущей основы в этой статье будет подробно проанализирован процесс аутентификации Spring Security.

Одним из основных элементов Spring Security является его цепочка фильтров. Мы начнем с его цепочки фильтров. На следующем рисунке показан процесс выполнения цепочки фильтров Spring Security. В этой статье мы будем следовать этому процессу, чтобы шаг за шагом проанализировать процесс аутентификации..

spring-security-filter

Введение в цепочку основных фильтров

Фильтров в Spring Security очень много.Вообще в обычных проектах фильтров больше десятка, а иногда включают и пользовательские фильтры.Конечно, мы не можем проанализировать каждый фильтр.Надо уловить ключевые моменты. , найдите несколько ключевых фильтров, которые играют важную роль в процессе аутентификации. Вот несколько основных фильтров:

  • SecurityContextPersistenceFilter: начало всей цепочки фильтров Spring Security, выполняет две функции: первая — проверять, когда приходит запросSessionсуществует вSecurityContext, если он не существует, создайте новыйSecurityContext. Во-вторых, когда запрос завершается,SecurityContextположить вSession, и ясноSecurityContextHolder.
  • UsernamePasswordAuthenticationFilter: унаследовано от абстрактного классаAbstractAuthenticationProcessingFilter, когда выполняется вход в форму, Фильтр инкапсулирует имя пользователя и пароль вUsernamePasswordAuthenticationаутентификация.
  • AnonymousAuthenticationFilter: Фильтр анонимной идентификации, если в текущем фильтре по-прежнему нет информации о пользователе после аутентификации, фильтр создаст анонимную идентификацию——AnonymousAuthenticationToken. Общая роль предназначена для анонимного входа.
  • ExceptionTranslationFilter: Фильтр преобразования исключений для обработкиFilterSecurityInterceptorВыброшено исключение.
  • FilterSecurityInterceptor: последний уровень цепочки фильтров, получение проверки подлинности из SecurityContextHolder и сравнение разрешений, которые имеет пользователь, с разрешениями, требуемыми доступными ресурсами.

Процесс аутентификации входа в форму

Когда мы получаем доступ к защищенному ресурсу, если ранее не выполнялась аутентификация входа, система вернет форму входа или результат ответа, предлагающий нам сначала войти в систему. Наш процесс анализа здесь предназначен только для входа в форму, поэтому мы сначала заполняем имя пользователя и пароль в форме для проверки входа.

Куча основных фильтров была кратко описана выше, здесь мы начнем сSecurityContextPersistenceFilterНачало этого фильтра начинает анализировать весь процесс аутентификации входа в форму.

SecurityContextPersistenceFilter

Когда мы закончим заполнение формы, нажмите кнопку входа, запрос пройдет первымSecurityContextPersistenceFilterФильтр, как упоминалось ранее, Фильтр имеет две функции, одна из которых заключается в созданииSecurityContextКонтекст безопасности, давайте посмотрим, как это делается внутри, часть исходного кода выглядит следующим образом:

public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";
    //安全上下文存储的仓库
	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
	    //使用HttpSession来存储 SecurityContext
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
        // 如果是第一次请求,request中肯定没有 FILTER_APPLIED属性
		if (request.getAttribute(FILTER_APPLIED) != null) {
			// 确保每个请求只应用一次过滤器
			chain.doFilter(request, response);
			return;
		}

		final boolean debug = logger.isDebugEnabled();
        // 在request 设置 FILTER_APPLIED 属性为 true,这样同一个请求再次访问时,就直接进入后续Filter的操作
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        
		if (forceEagerSessionCreation) {
			HttpSession session = request.getSession();

			if (debug && session.isNew()) {
				logger.debug("Eagerly created session: " + session.getId());
			}
		}
        // 封装 requset 和 response 
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
		// 从存储安全上下文的仓库中载入 SecurityContext 安全上下文,其内部是从 Session中获取上下文信息
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

		try {
		    //安全上下文信息设置到 SecurityContextHolder 中,以便在同一个线程中,后续访问 SecurityContextHolder 能获取到 SecuritContext
			SecurityContextHolder.setContext(contextBeforeChainExecution);
            //进入下一个过滤器操作
			chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		finally {
		    // 请求结束后,清空安全上下文信息
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
			// Crucial removal of SecurityContextHolder contents - do this before anything
			// else.
			SecurityContextHolder.clearContext();
			//将安全上下文信息存储到 Session中,相当于登录态的维护
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);

			if (debug) {
				logger.debug("SecurityContextHolder now cleared, as request processing completed");
			}
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}
}

Когда придет запрос, используйтеHttpSessionSecurityContextRepositoryПрочтите контекст безопасности. Мы здесь впервые, контекст безопасности чтения неAuthenticationидентификационную информацию, установите контекст безопасности наSecurityContextHolderПосле этого переходим к следующему фильтру.

В конце запроса используйте тот жеHttpSessionSecurityContextRepositoryРепозиторий, в котором хранится контекст безопасности, будет аутентифицированSecurityContextположить вSession, это тожеПоддержание состояния входаКлюч, конкретная операция здесь не рассматривается.

UsernamePasswordAuthenticationFilter

проходить черезSecurityContextPersistenceFilterфильтр появится позжеUsernamePasswordAuthenticationFilterfilter, так как мы предполагаем первый запрос, поэтомуSecurityContextне содержит сертифицированныхAuthentication.Действия, начинающиеся с этого фильтра, имеют решающее значение для входа в форму и содержат основные шаги аутентификации для входа в форму., схема процесса аутентификации в этом фильтре нарисована ниже:

filter-process

UsernamePasswordAuthenticationFilterРодительский классAbstractAuthenticationProcessingFilter, сначала введите родительский классfoFilterМетод, часть исходного кода выглядит следующим образом:

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
	...
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
        ...
		Authentication authResult;

		try {
		    //调用子类 UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法
			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) {
			//认证失败
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// 认证成功
		if (continueChainBeforeSuccessfulAuthentication) {
		    //继续调用下一个 Filter
			chain.doFilter(request, response);
		}
        //将成功认证后的Authentication写入 SecurityContext中
		successfulAuthentication(request, response, chain, authResult);
	}		
}

ДолженdoFilterОдним из основных методов является вызов подклассаUsernamePasswordAuthenticationFilterизattemptAuthenticationметод, который входит в реальный процесс аутентификации и возвращает аутентифицированныйAuthentication, исходный код этого метода выглядит следующим образом:

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
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
        //核心部分,交给内部的AuthenticationManager去认证,并返回认证后的 Authentication
		return this.getAuthenticationManager().authenticate(authRequest);
	}

Ключевым моментом в этом методе является то, чтоhis.getAuthenticationManager().authenticate(authRequest), позвоните во внутреннийAuthenticationManagerдля аутентификации в предыдущемстатьятолько что представилAuthenticationManager, который является основным интерфейсом аутентификации, его класс реализацииProviderManager,а такжеProviderManagerв свою очередь делегирует запросAuthenticationProviderList, каждый AuthenticationProvider в списке будет опрошен по очереди, нуждается ли он в аутентификации.Результат аутентификации каждого провайдера имеет только два случая: генерация исключения или полное заполнение всех свойств объекта Authentication.

Давайте проанализируем ключAuthenticationProvider, этоDaoAuthenticationProvider, который является самым ранним поставщиком платформы и наиболее часто используемым поставщиком. В большинстве случаев мы будем полагаться на него для аутентификации, его родительский класс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 {
			    1 //调用子类  DaoAuthenticationProvider 的 retrieveUser()方法获取 UserDetails
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			//没拿到UserDetails会抛出异常信息
			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 {
		    2 //对UserDetails的一些属性进行预检查,即判断用户是否锁定,是否可用以及用户是否过期
			preAuthenticationChecks.check(user);
			3 //对UserDetails附加的检查,对传入的Authentication与从数据库中获取的UserDetails进行密码匹配
			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;
			}
		}
        4 //对UserDetails进行后检查,检查UserDetails的密码是否过期
		postAuthenticationChecks.check(user);

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

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
        5 //上面所有检查成功后,用传入的用户信息和获取的UserDetails生成一个成功验证的Authentication
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

Из приведенной выше серии исходного кода извлеките несколько ключевых методов:

  • retrieveUser(...): вызовите метод retrieveUser() подкласса DaoAuthenticationProvider, чтобы получить сведения о пользователе.
  • preAuthenticationChecks.check(user): предварительно проверить UserDetails, полученные сверху, то есть определить, заблокирован ли пользователь, доступен ли он и не истек ли срок действия пользователя
  • additionalAuthenticationChecks(user,authentication): Дополнительные проверки UserDetails, совпадение паролей между входящей аутентификацией и полученными UserDetails.
  • postAuthenticationChecks.check(user): Постпроверить UserDetails, то есть проверить, не истек ли срок действия пароля UserDetails.
  • createSuccessAuthentication(principalToReturn, authentication, user): после того, как все вышеперечисленные проверки будут успешными, используйте входящую аутентификацию и полученные сведения о пользователе для создания успешно аутентифицированной аутентификации.

Метод получения пользователя (...)

Далее мы подробно обсудимretrieveUser(...)исходный код метода retrieveUser() для DaoAuthenticationProvider выглядит следующим образом:

protected final UserDetails retrieveUser(String username,
		UsernamePasswordAuthenticationToken authentication)
		throws AuthenticationException {
	prepareTimingAttackProtection();
	try {
	    //经过UserDetailsService 获取 UserDetails
		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);
	}
}

Основной частью этого метода является вызов внутренних UserDetailsServices для загрузки UserDetails,UserDetailsServicesПо сути, это интерфейс для загрузки UserDetails, который содержит более подробную информацию о пользователе, чем Authentication.Общие классы реализации UserDetailsService включают JdbcDaoImpl и InMemoryUserDetailsManager.Первый загружает пользователей из базы данных, а второй загружает пользователей из памяти. Мы также можем реализовать интерфейс UserDetailsServices самостоятельно, например, если мы основаны на аутентификации по базе данных, то мы можем реализовать этот интерфейс вручную вместо JdbcDaoImpl.

additionalAuthenticationChecks()

Предварительная и пост-проверка UserDetails относительно проста, поэтому я не буду здесь вдаваться в подробности. Давайте посмотрим на проверку совпадения паролей. Код выглядит следующим образом:

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();
        //利用 PasswordEncoder编码器校验密码
		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"));
		}
	}

Этот метод фактически вызываетDaoAuthenticationProviderизadditionalAuthenticationChecksметод, который внутренне вызывает шифрование/дешифратор для сопоставления паролей, и если сопоставление не удается, выдаетBadCredentialsExceptionаномальный

наконец прошлоcreateSuccessAuthentication(..)Метод генерирует успешно аутентифицированную аутентификацию, которая просто объединяет полученные UserDetails и входящую аутентификацию, чтобы получить полностью заполненную аутентификацию.

Аутентификация, наконец, шаг за шагом возвращается кAbstractAuthenticationProcessingFilterфильтр, установите его наSecurityContextHolder.

AnonymousAuthenticationFilter

Анонимный фильтр аутентификации, в основном для анонимного входа в систему, если предыдущий фильтр, такой какUsernamePasswordAuthenticationFilterПосле выполнения SecurityContext по-прежнему не имеет информации о пользователе, затемAnonymousAuthenticationFilterОн будет работать и генерировать анонимную идентификационную информацию — AnonymousAuthenticationToken.

ExceptionTranslationFilter

ExceptionTranslationFilterПроще говоря, это обработка исключения, генерируемого FilterSecurityInterceptor, и его внутреннегоdoFilterИсходный код метода выглядит следующим образом:

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
		    //直接进入下一个Filter
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex) {
			throw ex;
		}
		//真正的作用在这里,处理抛出的异常
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            //这里会处理 FilterSecurityInterceptor 抛出的AccessDeniedException
			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw new RuntimeException(ex);
			}
		}
	}

FilterSecurityInterceptor

FilterSecurityInterceptorФильтр — это последний уровень, сюда в конечном итоге придет предыдущий запрос, и его общий рабочий процесс

  • Инкапсулировать информацию о запросе
  • Чтение информации о конфигурации из системы, то есть информации о разрешениях, требуемой ресурсом.
  • отSecurityContextHolderПолучить ранее сертифицированныйAuthenticationОбъект, что означает разрешения, которые есть у текущего пользователя
  • Затем по трем видам информации, полученной выше, она передается в средство проверки разрешений, и для текущего запроса сравниваются разрешения, которые есть у пользователя, и разрешения, необходимые для ресурса. Если сравнение прошло успешно, оно войдет в логику обработки запросов реальной системы, в противном случае будет выдано соответствующее исключение.

Ниже приведена простая блок-схема, иллюстрирующаяFilterSecurityInterceptorПроцесс выполнения следующий:

filter_processs

Основываясь на приведенной выше картинке, давайте посмотримFilterSecurityInterceptorисходный код,

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
		Filter {
	 ...
	public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {
	// 封装request、response请求
	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) {
		// filter already applied to this request and user wants us to observe
		// once-per-request handling, so don't re-do security checking
		fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
	}
	else {
		// 判断当前请求之前是否经历过该过滤器
		if (fi.getRequest() != null && observeOncePerRequest) {
		//  如果当前请求已经经历过这个安全过滤器判断,那么不再执行后续逻辑,直接往下走,调用请求的处理方法
			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);
	}
}

}

Запрос был инкапсулирован в исходный код, а затем входит в основную часть, вызывая метод оценки авторизации родительского класса——beforeInvocation(FilterInvocation), исходный код выглядит следующим образом:

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());
		}
		//读取Spring Security的配置信息,将其封装成 ConfigAttribute
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
		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'");
			}
                            ...
			return null; // no further work post-invocation
		}
                ...
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}
		//从SecurityContextHolder中获取Authentication
		Authentication authenticated = authenticateIfRequired();

		// 启动授权匹配
		try {
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}
                ...
	    
	}

beforeInvocationЕсть много исходных кодов, я оставлю здесь только относительно основные части.Из исходного кода видно, что после получения информации о конфигурации и информации о пользователе они передаются вместе с информацией о запросе.AccessDecisionManagerизdecide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)метод.该方法是最终执行授权校验逻辑的地方。

Сам AccessDecisionManager - это интерфейс, его класс реализацииAbstractAccessDecisionManager,а такжеAbstractAccessDecisionManagerЭто также абстрактный класс, у него есть три класса реализации.AffirmativeBased, финальная логика проверки авторизации реализована AffirmativeBased.Часть исходного кода выглядит следующим образом:

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);
                ...
		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"));
	}
        ...
}

Логика этого метода относительно проста, то есть выполнитьAccessDecisionVoterЛогика проверки, выдает, если проверка не удаласьAccessDeniedExceptionаномальный. Для AccessDecisionVotervoteЛогика голосования здесь подробно описываться не будет, после Spring Security 3.0 она обычно используется по умолчанию.AccessDecisionVoterКласс реализации интерфейсаWebExpressionVoterдля завершения окончательного процесса проверки.

резюме

Начиная с фильтра, мы сделали довольно подробный анализ процесса аутентификации Spring Security, Конечно, есть еще много нераскрытых деталей.

Ссылки и благодарности