Search code examples
angularjsspring-securitycsrfcsrf-protection

Using AngularJS with SpringSecurity3.2 for CSRF


AngularJS

index.html

<head>
    <meta name="_csrf" content="${_csrf.token}"/>
    <!-- default header name is X-CSRF-TOKEN -->
    <meta name="_csrf_header" content="${_csrf.headerName}"/>
</head>

SpringSecurity 3.2

Spring uses HttpSessionCsrfTokenRepository which by default gives header name for CSRF as X-CSRF-TOKEN, however Anuglar convention is X-XSRF-TOKEN

I wanted to extend HttpSessionCsrfTokenRepository and override the header name, but since it is marked final I ended up implementing a custom token repository.

@Component
public class CustomCsrfTokenRepository implements CsrfTokenRepository {

  public static final String CSRF_PARAMETER_NAME = "_csrf";

  public static final String CSRF_HEADER_NAME = "X-XSRF-TOKEN";

  private final Map<String, CsrfToken> tokenRepository = new ConcurrentHashMap<>();

  public CustomCsrfTokenRepository() {
    log.info("Creating {}", CustomCsrfTokenRepository.class.getSimpleName());
  }

  @Override
  public CsrfToken generateToken(HttpServletRequest request) {
    return new DefaultCsrfToken(CSRF_HEADER_NAME, CSRF_PARAMETER_NAME, createNewToken());
  }

  @Override
  public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
    String key = getKey(request);
    if (key == null)
      return;

    if (token == null) {
      tokenRepository.remove(key);
    } else {
      tokenRepository.put(key, token);
    }
  }

  @Override
  public CsrfToken loadToken(HttpServletRequest request) {
    String key = getKey(request);
    return key == null ? null : tokenRepository.get(key);
  }

  private String getKey(HttpServletRequest request) {
    return request.getHeader("Authorization");
  }

  private String createNewToken() {
    return UUID.randomUUID().toString();
  }
}

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Inject
    private CustomCsrfTokenRepository customCsrfTokenRepository;


     @Override
        protected void configure(HttpSecurity http) throws Exception {

            http
    //          .addFilterAfter(new CsrfTokenGeneratorFilter(), CsrfFilter.class)
                .exceptionHandling()
                    .authenticationEntryPoint(authenticationEntryPoint)
                    .and()
                .formLogin()
                    .loginProcessingUrl("/app/authentication")
                    .successHandler(ajaxAuthenticationSuccessHandler)
                    .failureHandler(ajaxAuthenticationFailureHandler)
                    .usernameParameter("j_username")
                    .passwordParameter("j_password")
                    .permitAll()
                    .and()

                 .csrf()
                    .csrfTokenRepository(customCsrfTokenRepository)
                    .and()
              }
           }
  1. How can I cleanly override the header name instead of creating a custom csrfTokenRepository?

  2. Is there any other configuration changes I need to do for Single Page Applications such as AngularJS, as this does not work yet.


Solution

  • When using Java configuration for Spring Security, the following should be possible:

      public void configure(final HttpSecurity http) throws Exception
      {
        final HttpSessionCsrfTokenRepository tokenRepository = new HttpSessionCsrfTokenRepository();
        tokenRepository.setHeaderName("X-XSRF-TOKEN");
    
        http.csrf().csrfTokenRepository(tokenRepository);
      }
    

    The complication is that single-page applications rely on AJAX and including CSRF tokens with AJAX requests is a bit complicated. When using AngularJS, the server should send a session cookie called XSRF-TOKEN upon first request and whenever a user logs in or logs out. AngularJS will then return the value of this cookie in the HTTP header X-XSRF-TOKEN with all requests, which the server can then check.