SpringSecurity Filter CsrfFilter

CSRF (Cross-site request forgery), Chinese name: Cross-site request forgery, also known as one click attack/session riding, abbreviated as CSRF/XSRF.

You can understand CSRF attacks as follows: an attacker steals your identity and sends malicious requests on your behalf. CSRF can do things like send mail on your behalf, send messages, steal your account, even buy goods, transfer virtual currency.. Problems include: personal privacy leaks and property security.

CSRF has been proposed by foreign security personnel in 2000, but it has not been noticed in China until 2006. In 2008, many large community and interactive websites at home and abroad exploded CSRF vulnerabilities, such as NYTimes.com (New York Times), Metafilter (a large BLOG website), YouTube and Baidu HI.. Now, many sites on the Internet are so unprepared that the security industry calls the CSRF a "sleeping giant".

CSRF works by logging in to trusted Web site A, generating cookies locally, and then accessing dangerous Web site B without logging out of A.
Looking at this, you might say, "If I don't satisfy one of the above two conditions, I won't be attacked by CSRF."

Yes, it is, but you cannot guarantee that the following will not happen:
1. You can't guarantee that you won't open a tab page and visit another website after you log in to one website.
2. You cannot guarantee that your local Cookie will expire immediately after you close your browser and that your last session has ended. (In fact, closing a browser does not end a session, but most people mistakenly assume that closing a browser is equivalent to quitting a login/ending a session...)

The Spring Security Web uses CsrfFilter to resolve Cross-Site Request Forgery (CSRF) attacks using the Synchronizer token pattern (STP).

STP mode is meant to generate a different, random, unpredictable token for CSRF protection per request. This strict mode of CSRF protection is very strong. However, each request imposes an additional burden on the server side, and it also requires the browser to maintain the correct sequence of events, which can cause some usability problems (such as when a user has multiple tabs open). So Spring Security extends this restriction to one csrf token per session, and only for HTTP actions that update the status of the server: PATCH, POST, PUT,DELETE, and so on.

package org.springframework.security.web.csrf;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;


public final class CsrfFilter extends OncePerRequestFilter {
	/**
	 * The default RequestMatcher that indicates if CSRF protection is required or
	 * not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other
	 * requests.
	 * To detect which requests require csrf protection, the default configuration here is: GET, HEAD, TRACE, OPTIONS, which is read-only
	 * HTTP Verbs are ignored and not protected by csrf, while other HTTP services, such as PATCH, POST, PUT,DELETE, modify the state of the server
	 * Verbs are protected by the csrf of the current Filter.
	 */
	public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher();

	private final Log logger = LogFactory.getLog(getClass());
	private final CsrfTokenRepository tokenRepository;
	private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
	// Processing for CSRF protection validation logic failures
	private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

	// Constructor to construct a CsrfFilter instance using the specified csrf token repository
	// By default, use Spring Security's Springboot web application and choose to use
	// csrfTokenRepository is a HttpSessionCsrfTokenRepository instance that has been lazily encapsulated.
	// That is, the corresponding csrf token is saved in the http session.	
	public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
		Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
		this.tokenRepository = csrfTokenRepository;
	}

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

		// Get the csrf token for the current request from the csrf token repository.
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		// Record whether csrf token does not exist for the current request
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			// If the csrf token for the current request does not already exist in the repository, generate one and associate it with the
			// The current request is saved to the csrf token repository
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}

		// Save the csrf token obtained from or newly created and saved to the repository as two properties of the request
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);
		
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			// Detects whether the current request requires csrf protection and, if not, lets you continue with other filter chain logic
			filterChain.doFilter(request, response);
			return;
		}

		// Try to get the actual csrf token passed in from the browser side from the request header or parameter.
		// By default, use header name: X-CSRF-TOKEN when removing from the head
		// The parameter name used to get the parameter from the request is: _ csrf
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!csrfToken.getToken().equals(actualToken)) {
			// There are two cases where tokens taken out of the csrf token repository are not equal to tokens passed in from the browser side:
			// 1. There is no csrf token in the repository for this request
			// 2. The actual inconsistency between csrf token and request parameters carried in the repository for the request
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				// 1. For this request, there is no csrf token in the repository, the processing scheme:
				// Throw Exception MissingCsrfTokenException
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				// 2. For the inconsistency between the csrf token in the repository and the actual carrying of the request parameters, the processing scheme:
				// Throw Exception InvalidCsrfTokenException
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}
		
		// The current request needs csrf validation logic for the Filter and csrf validation to pass, release, and continue with the filter chain
		// Other part logic
		filterChain.doFilter(request, response);
	}

	/**
	 * Specifies a RequestMatcher that is used to determine if CSRF protection
	 * should be applied. If the RequestMatcher returns true for a given request,
	 * then CSRF protection is applied.
	 *
	 * Specify a RequestMatcher to detect whether a request needs to apply csrf-protected validation logic.
	 * 
	 * The default is to apply CSRF protection for any HTTP method other than GET, HEAD,
	 * TRACE, OPTIONS.
	 * The default behavior is to validate other read-only HTTP requests against GET, HEAD,TRACE, OPTIONS without csrf protection validation
	 * HTTP requests that update the state of the server, such as PATCH, POST, PUT,DELETE, and so on.
	 * 
	 *
	 * @param requireCsrfProtectionMatcher the RequestMatcher used to determine if
	 * CSRF protection should be applied.
	 */
	public void setRequireCsrfProtectionMatcher(
			RequestMatcher requireCsrfProtectionMatcher) {
		Assert.notNull(requireCsrfProtectionMatcher,
				"requireCsrfProtectionMatcher cannot be null");
		this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
	}

	/**
	 * Specifies a AccessDeniedHandler that should be used when CSRF protection
	 * fails.
	 * Specify an AccessDeniedHandler to handle CSRF protection validation logic failures.
	 *
	 * The default is to use AccessDeniedHandlerImpl with no arguments.
	 * The default behavior is to use an AccessDeniedHandlerImpl instance with more than one parameter.
	 *
	 * @param accessDeniedHandler the AccessDeniedHandler to use
	 */
	public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
		Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
		this.accessDeniedHandler = accessDeniedHandler;
	}
	 // RequestMatcher to detect which HTTP requests require csrf protection.
	 // The default behavior is to not protect csrf against read-only HTTP requests such as GET, HEAD,TRACE, OPTIONS.
	 // Other HTTP requests that update the state of the server, such as PATCH, POST, PUT,DELETE, require csrf protection.
	private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
		private final HashSet<String> allowedMethods = new HashSet<>(
				Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
	
		@Override
		public boolean matches(HttpServletRequest request) {
			return !this.allowedMethods.contains(request.getMethod());
		}
	}
}



Keywords: Spring Security security Web Security

Added by BinaryDragon on Mon, 03 Jan 2022 22:17:47 +0200