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

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

Предисловие:

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

1. Схема цепочки фильтров

После настройки безопасности Spring DefaultSecurityFilterChain будет выводить соответствующий журнал при запуске проекта:

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters){
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<Filter>(filters);
	}

  • Выводится следующий журнал:
[main] o.s.s.web.DefaultSecurityFilterChain     :
Creating filter chain:
org.springframework.security.web.util.matcher.AnyRequestMatcher@1,
[
  org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@184de357,
  org.springframework.security.web.context.SecurityContextPersistenceFilter@521ba38f,
  org.springframework.security.web.header.HeaderWriterFilter@77bb916f,
  org.springframework.security.web.csrf.CsrfFilter@76b305e1,
  org.springframework.security.web.authentication.logout.LogoutFilter@17c53dfb,
  org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2086d469,
  org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@b1d19ff,
  org.springframework.security.web.authentication.AnonymousAuthenticationFilter@efe49ab,
  org.springframework.security.web.session.SessionManagementFilter@5a48d186,
  org.springframework.security.web.access.ExceptionTranslationFilter@273aaab7
]

Вы также можете просмотреть его из Debug:

Debug查看

Во-вторых, анализ фильтра один за другим

Прежде чем разбирать, давайте поговорим о двух важнейших классах: OncePerRequestFilter и GenericFilterBean, в фильтре цепочки фильтров, более или менее косвенно или прямо унаследованном от

  • OncePerRequestFilter, как следует из названия, может гарантировать, что запрос проходит через фильтр только один раз, и его не нужно выполнять повторно.
  • GenericFilterBean — это базовый класс реализации интерфейса javax.servlet.Filter.
    • GenericFilterBean использует элемент конфигурации параметра-init-param в теге фильтра в web.xml в качестве свойства компонента.
    • GenericFilterBean может быть просто родительским классом любого типа фильтра.
    • Подклассы GenericFilterBean могут настраивать некоторые необходимые им свойства.
    • GenericFilterBean, который оставляет фактическую работу по фильтрации своим подклассам, что приводит к тому, что его подклассы должны реализовывать метод doFilter.
    • GenericFilterBean не зависит от Spring ApplicationContext.Фильтры обычно не читают напрямую информацию о своем контейнере (концепция ApplicationContext), а получают ее, обращаясь к сервисным компонентам в контейнере Spring (контекст корневого приложения Spring), обычно вызывая getServletContext в методе filter() для получения

2.1.WebAsyncManagerIntegrationFilter

  public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {
		......
    @Override
    protected void doFilterInternal(HttpServletRequest request,
        HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

      SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
          .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
      if (securityProcessingInterceptor == null) {
        asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
            new SecurityContextCallableProcessingInterceptor());
      }
      filterChain.doFilter(request, response);
    }
  }

Из исходного кода мы можем проанализировать связанные функции WebAsyncManagerIntegrationFilter:

  • Получить WebAsyncManager согласно пакету запроса
  • Получить/зарегистрировать SecurityContextCallableProcessingInterceptor из WebAsyncManager

2.2.SecurityContextPersistenceFilter

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

			if (request.getAttribute(FILTER_APPLIED) != null) {
				// ensure that filter is only applied once per request
				chain.doFilter(request, response);
				return;
			}

			final boolean debug = logger.isDebugEnabled();

			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

			if (forceEagerSessionCreation) {
				HttpSession session = request.getSession();

				if (debug && session.isNew()) {
					logger.debug("Eagerly created session: " + session.getId());
				}
			}

			HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
					response);
			SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

			try {
				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();
				repo.saveContext(contextAfterChainExecution, holder.getRequest(),
						holder.getResponse());
				request.removeAttribute(FILTER_APPLIED);

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

Из исходного кода мы можем проанализировать связанные функции SecurityContextPersistenceFilter:

  1. Первый экземпляр SecurityContextHolder->HttpSessionSecurityContextRepository (репозиторий заменяется ниже) Функция: извлекает информацию о аутентифицированном пользователе из сеанса, повышает эффективность и позволяет избежать запроса информации об аутентификации пользователя для каждого запроса.
  2. Создайте HttpRequestResponseHolder из запроса и ответа
  3. Репо получает SecurityContext в соответствии с контекстом загрузки HttpRequestResponseHolder.
  4. SecurityContextHolder устанавливает полученный SecurityContext в контекст, а затем продолжает выполнять другие фильтры вниз.
  5. наконец->SecurityContextHolder получает SecurityContext, затем очищает его и сохраняет его и информацию о запросе в репо, удаляет атрибут FILTER_APPLIED из запроса

2.3.HeaderWriterFilter

public class HeaderWriterFilter extends OncePerRequestFilter {
	......
	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		for (HeaderWriter headerWriter : headerWriters) {
			headerWriter.writeHeaders(request, response);
		}
		filterChain.doFilter(request, response);
	}
}

Из исходного кода мы можем проанализировать функции, связанные с HeaderWriterFilter:

  • Добавьте соответствующую информацию в заголовок запроса и используйте заголовки security: внутри тега http для управления

2.4.CsrfFilter

public final class CsrfFilter extends OncePerRequestFilter {
	......
	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);

		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!csrfToken.getToken().equals(actualToken)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}

		filterChain.doFilter(request, response);
	}
		......
}

Из исходного кода мы можем проанализировать функции, связанные с CsrfFilter:

  • Csrf также известен как подделка междоменных запросов, когда злоумышленник получает доступ к доверенным сайтам, подделывая запросы пользователей.
  • Убедитесь, что информация о токене csrf включена в запрос, который необходимо проверить. Если нет, будет сообщено об ошибке. Таким образом, атакующий веб-сайт не может получить информацию о токене, а информация, отправленная через домен, не может пройти проверку фильтра.

2.5.LogoutFilter

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

		if (requiresLogout(request, response)) {
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();

			if (logger.isDebugEnabled()) {
				logger.debug("Logging out user '" + auth
						+ "' and transferring to logout destination");
			}

			this.handler.logout(request, response, auth);

			logoutSuccessHandler.onLogoutSuccess(request, response, auth);

			return;
		}

		chain.doFilter(request, response);
	}
	......
}

Из исходного кода мы можем проанализировать связанные функции LogoutFilter:

  • URL соответствия, по умолчанию /logout
  • После успешного совпадения пользователь выходит, и информация об аутентификации очищается.

2.6.RequestCacheAwareFilter

public class RequestCacheAwareFilter extends GenericFilterBean {
  ......
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
				(HttpServletRequest) request, (HttpServletResponse) response);

		chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
				response);
	}
}

Из исходного кода мы можем проанализировать связанные функции RequestCacheAwareFilter:

  • RequestCache поддерживается внутри через HttpSessionRequestCache для кэширования HttpServletRequest.

2.7.SecurityContextHolderAwareRequestFilter

public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
  ......
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
				throws IOException, ServletException {
			chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
					(HttpServletResponse) res), res);
	}
	......
}

Из исходного кода мы можем проанализировать связанные функции SecurityContextHolderAwareRequestFilter:

  • Для ServletRequest сделана оболочка, которая делает запрос более богатым API.

2.8.AnonymousAuthenticationFilter

public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {
		......
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
				throws IOException, ServletException {

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

				if (logger.isDebugEnabled()) {
					logger.debug("Populated SecurityContextHolder with anonymous token: '"
							+ SecurityContextHolder.getContext().getAuthentication() + "'");
				}
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
							+ SecurityContextHolder.getContext().getAuthentication() + "'");
				}
			}

			chain.doFilter(req, res);
	}
	......
}

Из исходного кода мы можем проанализировать функции, связанные с AnonymousAuthenticationFilter:

  • Когда информация аутентификации в SecurityContextHolder пуста, анонимный пользователь будет создан и сохранен в SecurityContextHolder. Фильтр анонимной идентификации, который я лично считаю очень важным.Чтобы понять его, его нужно объединить с UsernamePasswordAuthenticationFilter.Чтобы быть совместимым с незарегистрированным доступом, Spring Security также использует процесс аутентификации, но это просто анонимная личность.
  • Анонимный фильтр аутентификации, некоторые люди могут подумать: Анонимность и личность? Личное понимание анонимной анонимной идентичности заключается в том, что Spirng Security предоставляет анонимную идентичность даже неаутентифицированным пользователям для единства общей логики. Расположение фильтра AnonymousAuthenticationFilter также очень научно, оно расположено после часто используемых фильтров аутентификации (таких как UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter, RememberMeAuthenticationFilter), что означает, что только после выполнения вышеупомянутого фильтра идентификации SecurityContext по-прежнему не имеет информации о пользователе, AnonymousAuthenticationFilter Этот фильтр имеет смысл только на основе анонимной личности пользователя.

2.9.SessionManagementFilter

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

			if (request.getAttribute(FILTER_APPLIED) != null) {
				chain.doFilter(request, response);
				return;
			}

			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

			if (!securityContextRepository.containsContext(request)) {
				Authentication authentication = SecurityContextHolder.getContext()
						.getAuthentication();

				if (authentication != null && !trustResolver.isAnonymous(authentication)) {
					// The user has been authenticated during the current request, so call the
					// session strategy
					try {
						sessionAuthenticationStrategy.onAuthentication(authentication,
								request, response);
					}
					catch (SessionAuthenticationException e) {
						// The session strategy can reject the authentication
						logger.debug(
								"SessionAuthenticationStrategy rejected the authentication object",
								e);
						SecurityContextHolder.clearContext();
						failureHandler.onAuthenticationFailure(request, response, e);

						return;
					}
					// Eagerly save the security context to make it available for any possible
					// re-entrant
					// requests which may occur before the current request completes.
					// SEC-1396.
					securityContextRepository.saveContext(SecurityContextHolder.getContext(),
							request, response);
				}
				else {
					// No security context or authentication present. Check for a session
					// timeout
					if (request.getRequestedSessionId() != null
							&& !request.isRequestedSessionIdValid()) {
						if (logger.isDebugEnabled()) {
							logger.debug("Requested session ID "
									+ request.getRequestedSessionId() + " is invalid.");
						}

						if (invalidSessionStrategy != null) {
							invalidSessionStrategy
									.onInvalidSessionDetected(request, response);
							return;
						}
					}
				}
			}
		chain.doFilter(request, response);
	}
	......
}

Из исходного кода мы можем проанализировать связанные функции SessionManagementFilter:

  • securityContextRepository ограничивает количество сеансов, открытых одним пользователем
  • SessionAuthenticationStrategy предотвращает атаки защиты с фиксацией сеанса (защищает неанонимных пользователей)

2.10.ExceptionTranslationFilter

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

		try {
			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);

			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if (ase != null) {
				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);
			}
		}
	}
	......
}

Из исходного кода мы можем проанализировать функции, связанные с ExceptionTranslationFilter:

  • Фильтр преобразования исключений ExceptionTranslationFilter расположен за всем springSecurityFilterChain и используется для преобразования исключений, которые появляются во всей цепочке.
  • Функция этого фильтра состоит в том, чтобы обработать исключение, созданное FilterSecurityInterceptor, а затем перенаправить запрос на соответствующую страницу или вернуть соответствующий код ошибки ответа.

2.11.FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
		......
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
		return this.securityMetadataSource;
	}

	public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}
	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 {
				// first time this request being called, so perform security checking
				if (fi.getRequest() != null) {
					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);
			}
	}
	......
}

Из исходного кода мы можем проанализировать связанные функции FilterSecurityInterceptor:

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

2.12.UsernamePasswordAuthenticationFilter

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
		......
		public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
			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);

			return this.getAuthenticationManager().authenticate(authRequest);
	}
	......
}

Из исходного кода мы можем проанализировать связанные функции UsernamePasswordAuthenticationFilter:

  • Аутентификация с помощью формы — это наиболее часто используемый метод аутентификации.Один из наиболее интуитивно понятных бизнес-сценариев — разрешить пользователям вводить имя пользователя и пароль в форме для входа в систему. , характер