This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Writing in the front

In this section we will move into some advanced learning. Why say advanced basically these things can guarantee your service security, also have very practical significance.

In this section, we will learn about CSRF protection

Please check out my columns, my ongoing columns on Security and queue concurrency, and my design patterns (finished)

What is CSRF?

Let’s look at CSRF first. CSRF stands for Cross-site Request Forgery. So, what is cross-site request forgery, and how do we deal with it?

From a security perspective, you can think of CSRF as an attack in which an attacker steals your identity and sends malicious requests to third-party websites on your behalf. We can use the following flow chart to describe CSRF:

The specific process is as follows:

  • The user browses and logs in to the trusted website A. After the user is authenticated, A Cookie is generated for the website A in the browser.

  • The user visits site B without exiting Site A, and then Site B makes A request to Site A;

  • The user’s browser carries the Cookie to visit website A according to the request of website B.

  • Since the browser will automatically bring the user’s Cookie, after receiving the request, Website A will conduct access control according to the user’s permissions, which is equivalent to the user’s access to website A, so that Website B can simulate the operation process of the user’s access to website A.

Obviously, from the perspective of application development, CSRF is a security hole in the system, and this kind of security hole also exists widely in Web development.

Based on the workflow of CSRF, the basic idea of CSRF protection is to add a random value to each connection request in the system, which we call CSRF_Token. In this way, when A user sends A request to Site A, site A sets A CSRF_Token value in the Cookie it generates. When the browser sends the request, there is also A hidden CSRF_Token value in the submitted form data. In this way, after receiving the request, website A extracts the CSRF_Token from the Cookie on the one hand, and obtains the hidden CSRF_Token from the submitted data on the other hand. Compare the two, and if they don’t match, it’s a bogus request.

Now let’s look at ways to protect your application

Secure the application with CsrfFilter

In Spring Security, a CsrfFilter is provided for CSRF protection. CsrfFilter intercepts requests and allows requests using HTTP methods such as GET, HEAD, TRACE, and OPTIONS. For other requests such as PUT, POST, and DELETE that might modify the data, CsrfFilter wants to receive headers containing cSRF_Token. If the header does not exist or contains an incorrect CSRF_Token value, the application rejects the request and sets the status of the response to 403.

Looking at this, you might ask, what does this CSRF_Token look like? It’s essentially a string. In Spring Security, a CsrfToken interface is specifically defined to specify its format:

public interface CsrfToken extends Serializable {
 
    // Get the header name
    String getHeaderName(a);
 
    // Get the parameter name that should contain the Token
    String getParameterName(a);
	 
	// Get the Token value
    String getToken(a);
}
Copy the code

In the CsrfFilter class, we also find the following CsrfToken processing:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                     throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);
 
        // Get the CsrfToken from CsrfTokenRepository
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        final boolean missingToken = csrfToken == null;
 
        // If no CsrfToken is found, generate one and store it in CsrfTokenRepository
        if (missingToken) {
             csrfToken = this.tokenRepository.generateToken(request);
             this.tokenRepository.saveToken(csrfToken, request, response);
        }
 
        // Add the CsrfToken to the request
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);
 
        if (!this.requireCsrfProtectionMatcher.matches(request)) {
             filterChain.doFilter(request, response);
             return;
        }
 
        // Get the CsrfToken from the request
        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
             actualToken = request.getParameter(csrfToken.getParameterName());
        }
 
        // If the request carries a CsrfToken different from the one obtained from Repository, an exception is thrown
        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;
        }
        
        // Under normal circumstances continue with the subsequent flow of the filter chain
        filterChain.doFilter(request, response);
}
Copy the code

The whole filter execution process is relatively clear, basically around the CsrfToken verification work.

We noticed that introduces a CsrfTokenRepository here, this the Repository component implements the CsrfToken storage management, which includes the aforementioned CookieCsrfTokenRepository for cookies.

From CookieCsrfTokenRepository, first of all, we can see a set of constants defined, including against CSRF Cookie name and name of the header name, parameters, as shown below:

static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";
static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";
Copy the code

CookieCsrfTokenRepository saveToken () method is more simple, is based on the Cookie object CsrfToken work Settings, as shown below:

@Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        String tokenValue = token == null ? "" : token.getToken();
        Cookie cookie = new Cookie(this.cookieName, tokenValue);
        cookie.setSecure(request.isSecure());
        if (this.cookiePath ! =null&&!this.cookiePath.isEmpty()) {
                 cookie.setPath(this.cookiePath);
        } else {
                 cookie.setPath(this.getRequestContext(request));
        }
        if (token == null) {
             cookie.setMaxAge(0);
        }
        else {
             cookie.setMaxAge(-1);
        }
        cookie.setHttpOnly(cookieHttpOnly);
        if (this.cookieDomain ! =null&&!this.cookieDomain.isEmpty()) {
             cookie.setDomain(this.cookieDomain);
        }
 
        response.addCookie(cookie);
}
Copy the code

In the Spring Security, CsrfTokenRepository interface has a number of implementation class, besides CookieCsrfTokenRepository, and HttpSessionCsrfTokenRepository etc., I’m not going to go through them all.

Now that you know the basic implementation flow of CsrfFilter, let’s move on to discussing how to use it to implement CSRF protection. Starting with Spring Security 4.0, CSRF protection is enabled by default to prevent CSRF from attacking applications. The Spring Security CSRF defends the POST, PUT, and DELETE methods.

So, as far as developers are concerned, you don’t actually need to do any extra work to use this feature. Of course, if you don’t want to use this feature.

You can also disable the function by using the following configuration methods:

// This code should be seen frequently in your own projects
http.csrf().disable();
Copy the code

How do I customize CSRF protection

So how do we go about customizing a layer on top of that

Based on the previous discussion, if you want to retrieve a CsrfToken in an HTTP request, just use the code shown below:

CsrfToken token = (CsrfToken)request.getAttribute("_csrf");
Copy the code

If you don’t want to use Spring Security’s built-in storage and want to store CsrfToken based on your needs, the only thing to do is implement the CsrfTokenRepository interface.

Here we try to store CsrfToken in a relational database, so we can define a JpaTokenRepository by extending JpaRepository in Spring Data, as follows:

// Store information
public interface JpaTokenRepository extends JpaRepository<Token.Integer> {
    Optional<Token> findTokenByIdentifier(String identifier);
}
Copy the code

JpaTokenRepository is simple, with only a query method that obtains tokens based on identifiers, while the new interface is provided by default at JpaRepository and can be used directly.

Then, we based on JpaTokenRepository to build a DatabaseCsrfTokenRepository, as shown below:

public class DatabaseCsrfTokenRepository
        implements CsrfTokenRepository {
 
    @Autowired
    private JpaTokenRepository jpaTokenRepository;
 
    @Override
    public CsrfToken generateToken(HttpServletRequest httpServletRequest) {
        String uuid = UUID.randomUUID().toString();
        return new DefaultCsrfToken("X-CSRF-TOKEN"."_csrf", uuid);
    }
 
    @Override
    public void saveToken(CsrfToken csrfToken, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        String identifier = httpServletRequest.getHeader("X-IDENTIFIER");
        Optional<Token> existingToken = jpaTokenRepository.findTokenByIdentifier(identifier);
 
        if (existingToken.isPresent()) {
            Token token = existingToken.get();
            token.setToken(csrfToken.getToken());
        } else {
            Token token = newToken(); token.setToken(csrfToken.getToken()); token.setIdentifier(identifier); jpaTokenRepository.save(token); }}@Override
    public CsrfToken loadToken(HttpServletRequest httpServletRequest) {
        String identifier = httpServletRequest.getHeader("X-IDENTIFIER");
        Optional<Token> existingToken = jpaTokenRepository.findTokenByIdentifier(identifier);
 
        if (existingToken.isPresent()) {
            Token token = existingToken.get();
            return new DefaultCsrfToken("X-CSRF-TOKEN"."_csrf", token.getToken());
        }
 
        return null; }}Copy the code

DatabaseCsrfTokenRepository basic class code is self-explanatory, here with the help of the “X – IDENTIFIER” request header in the HTTP request to determine the unique identification of the request, which will be a unique IDENTIFIER associated with a particular CsrfToken. We then use JpaTokenRepository to persist a relational database.

Into effect in the end, to the code above, we need to be finished by the method of configuration of CSRF set, as shown below, this directly through csrfTokenRepository DatabaseCsrfTokenRepository method to integrate the custom:

@Override
protected void configure(HttpSecurity http) throws Exception {
        http.csrf(c -> {
            c.csrfTokenRepository(databaseCsrfTokenRepository());
        });
        // do not write... }Copy the code

To conclude, we can use the following schematic diagram to comb through the components of the overall custom CSRF and their relationships:

conclusion

Ok, that’s all for today, and next time we’ll continue our study of cross-domains

overtones

Thank you for reading, and if you feel like you’ve learned something, you can like it and follow it. Also welcome to have a question we comment below exchange

Come on! See you next time!

To share with you a few I wrote in front of a few SAO operation

Copy object, this operation is a little SAO!

Dry! SpringBoot uses listening events to implement asynchronous operations