CSRF of Spring Security

foreword

The previous article explained functions such as user logout and automatic login (remember me). Today we will take a look at the use and avoidance of CSRF.

What is CSRF

Cross-site request forgery (English: Cross-site request forgery), also known as one-click attack or session riding, usually abbreviated as CSRF or XSRF, is a method that coerces users to execute unintentional The attack method of the operation. Compared with cross-site scripting (XSS), which uses the user's trust in the specified website, CSRF uses the website's trust in the user's web browser.

Cross-site request attack, simply put, is that the attacker uses some technical means to deceive the user's browser to visit a website that he has authenticated and perform some operations (such as sending messages, sending emails, and even property operations such as purchasing goods and transferring money) ). Since the browser has been authenticated, the visited website will be considered to be a real user operation and run. This exploits a vulnerability in user authentication on the web: simple authentication can only guarantee that the request is from a user's browser, but not that the request itself is voluntary. Starting with Spring Security 4.0, CSRF protection is enabled by default to prevent CSRF from attacking applications, and Spring Security CSRF protects against PATCH, POST, PUT and DELETE methods.

Note that GET, HEAD, TRACE, and OPTIONS requests are not included here, and GET, HEAD, TRACE, and OPTIONS requests still have this problem.

Spring Security implements the principle of CSRF

1. Generate csrfToken and save it in Cookie or HttpSession. The SaveOnAccessCsrfToken class has an interface CsrfTokenRepository

Interface implementation class: HttpSessionCsrfTokenRepository, CookieCsrfTokenRepository

2. When the request arrives, extract the csrfToken from the request, and compare it with the saved csrfToken to determine whether the current request is legal. It is mainly done through the CsrfFilter filter.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.security.web.csrf;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashSet;
import java.util.function.Supplier;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.crypto.codec.Utf8;
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 {
    public static final RequestMatcher DEFAULT_CSRF_MATCHER = new CsrfFilter.DefaultRequiresCsrfMatcher();
    private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfFilter.class.getName();
    private final Log logger = LogFactory.getLog(this.getClass());
    private final CsrfTokenRepository tokenRepository;
    private RequestMatcher requireCsrfProtectionMatcher;
    private AccessDeniedHandler accessDeniedHandler;

    public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
        this.requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
        this.accessDeniedHandler = new AccessDeniedHandlerImpl();
        Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
        this.tokenRepository = csrfTokenRepository;
    }

    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER));
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        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)) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);
            }

            filterChain.doFilter(request, response);
        } else {
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }

            if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
                this.logger.debug(LogMessage.of(() -> {
                    return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);
                }));
                AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);
                this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }

    public static void skipRequest(HttpServletRequest request) {
        request.setAttribute(SHOULD_NOT_FILTER, Boolean.TRUE);
    }

    public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) {
        Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null");
        this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
    }

    public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
        Assert.notNull(accessDeniedHandler, "accessDeniedHandler cannot be null");
        this.accessDeniedHandler = accessDeniedHandler;
    }

    private static boolean equalsConstantTime(String expected, String actual) {
        if (expected == actual) {
            return true;
        } else if (expected != null && actual != null) {
            byte[] expectedBytes = Utf8.encode(expected);
            byte[] actualBytes = Utf8.encode(actual);
            return MessageDigest.isEqual(expectedBytes, actualBytes);
        } else {
            return false;
        }
    }

    private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
        private final HashSet<String> allowedMethods;

        private DefaultRequiresCsrfMatcher() {
            this.allowedMethods = new HashSet(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));
        }

        public boolean matches(HttpServletRequest request) {
            return !this.allowedMethods.contains(request.getMethod());
        }

        public String toString() {
            return "CsrfNotRequired " + this.allowedMethods;
        }
    }
}

 Well, that's all about Spring Security's CSRF attack prevention today.

Welcome everyone to click on the card below to pay attention to "coder trainees"

おすすめ

転載: blog.csdn.net/ybb_ymm/article/details/130155913