jeudi 8 octobre 2015

Spring Rest et le jeton CSRF

Nous allons voir comment utiliser des jetons CSRF dans une application REST avec spring.
Dans ce type d'application, il n'y a pas de formulaire pour entrer un nom d'utilisateur et un mot de passe.

Lors de votre connexion au serveur, il est possible d'obtenir le jeton CSRF au lieu de faire une requête au serveur.

    var req = new XMLHttpRequest();
    req.open('GET', document.location, false);
    req.send(null);
    var csrf = req.getResponseHeader("x-csrf-token")

    var cookie = JSON.stringify({method: 'GET', url: '/', csrf: csrf});
    $.cookie('myWebApp', cookie);

Ensuite pour la connexion

    var cookie = JSON.parse($.cookie('myWebApp'));
    var data = 'username=' + $('#username').val() + '&password=' + $('#password').val();
    $.ajax({
        data: data,
        headers: {'X-CSRF-TOKEN': cookie.csrf},
        timeout: 1000,
        type: 'POST',
        url: '/login',
    }).done(function (data, textStatus, jqXHR) {
        var csrf = jqXHR.getResponseHeader("x-csrf-token")
        var cookie = JSON.stringify({method: 'GET', url: '/', csrf: csrf});
        $.cookie('chezlise', cookie);
        window.location = "main.html";
    })

A chaque requête vous devez vérifier si le jeton a changé et l'affecter à votre cookie au besoin.

Mis à part les requêtes de type GET, il faudra insérer le jeton dans vos requête ajax.

Au niveau du serveur, il faut créer une classe héritant de WebSecurityConfigurerAdapter

 @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ServerApplicationSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private UserServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/rest/**").authenticated();
        //http.csrf().disable();
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        http.formLogin().successHandler(authenticationSuccessHandler);
        http.formLogin().failureHandler(authenticationFailureHandler);
        http.logout().logoutUrl("/logout");
        http.logout().logoutSuccessUrl("/");
        
        // CSRF tokens handling
        http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);

    }
}

https://github.com/aditzel/spring-security-csrf-filter/blob/master/src/main/java/com/allanditzel/springframework/security/web/csrf/CsrfTokenResponseHeaderBindingFilter.java

Ensuite dans la classe de l'application

@EntityScan(basePackageClasses = {ServerApplication.class, Jsr310JpaConverters.class})
@SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }

    @Bean
    public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
        return new ServerApplicationSecurity();
    }

}