Securing Your Web App with CSRF and CORS in Spring Security

Hey there! Have you ever heard of CSRF and CORS in Spring Security? These are two important security concepts that help protect our applications and users from malicious attacks. Let me explain them in a more relaxed and conversational way.

CSRF

Imagine a user is logged in to their bank account and then visits a malicious website that has a hidden form that automatically submits a transfer request to the user's bank account without their knowledge or consent.

CSRF attacks can occur on the same page or across different windows or tabs, as long as the user is logged into the application on both pages. The attack is not specific to a particular page or window/tab. It is important to implement CSRF protection to prevent malicious actions from being executed by attackers, regardless of which page or window/tab the user is currently on.

To prevent such attacks, Spring Security provides CSRF protection, which involves adding a unique token after the user sends a GET request to see the webpage. Then, the app only accepts mutating requests (like POST, PUT, DELETE) that contain this special token in the header. This proves that the request is coming from the app and not some shady external system.

The CsrfFilter is like the security guard that checks all incoming requests. It allows GET, HEAD, TRACE, and OPTIONS requests to go through, but for all the others, it expects to receive a header containing the token. If it's missing or incorrect, the request gets rejected with a big ol' 403 Forbidden status code.

So, where does this token come from, you ask? It's nothing fancy, just a string value that gets added to the header of the request. But don't forget to include it, otherwise, the app won't accept your request!

Spring Security has two main interfaces that you must implement to handle CSRF protection. These are the CsrfToken and CsrfTokenRepository interfaces. The CsrfToken interface describes the token itself, while the CsrfTokenRepository interface defines the object that creates, stores, and loads the CSRF tokens. The CsrfToken interface has three important properties that you need to specify, such as the name of the header in the request that contains the token's value (by default named X-CSRF-TOKEN), the name of the attribute of the request that stores the value of the token (by default named _csrf), and the value of the token.


Cross Origin Resource Sharing

Picture this: you're building a cool frontend web app using something fancy like Angular, ReactJS, or Vue. You're hosting it at a domain like example.com, but your app needs to call endpoints on a backend hosted at another domain, like api.example.com.  Without CORS, the browser would block the request due to the same-origin policy, which restricts web pages from making requests to a different domain than the one that served the page. However, if CORS is implemented correctly, the request can be made and the data can be retrieved, ensuring that the user's data and privacy is not compromised.

CORS (Cross-Origin Resource Sharing) works by using HTTP headers (fancy, right?). There are a few key headers to keep in mind:

  • Access-Control-Allow-Origin: This specifies which foreign domains (origins) are allowed to access resources on your domain.
  • Access-Control-Allow-Methods: This lets you specify which HTTP methods are allowed for a different domain to access. Maybe you want to let example.com call some endpoint, but only with HTTP GET. This header's got you covered.
  • Access-Control-Allow-Headers: This puts some limits on which headers you can use in a specific request.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("https://api.example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Disposition"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

This code snippet enables CORS and sets the allowed origins to "https://api.example.com". It also sets the allowed HTTP methods to GET, POST, PUT, and DELETE, and the allowed headers to Authorization and Content-Type. The exposed headers are set to Authorization and Content-Disposition. Finally, the corsConfigurationSource method is overridden to create a new CorsConfiguration object with the allowed origins, methods, and headers, and register it with the URL-based configuration source.


There you have it! With CORS, you can ensure that your frontend and backend are playing nicely together, and keep any sneaky hackers from causing trouble.