web-dev-qa-db-fra.com

Avec Spring Security 3.2.0.RELEASE, comment puis-je obtenir le jeton CSRF dans une page qui est purement HTML sans bibliothèque de balises

Aujourd'hui, j'ai effectué une mise à niveau de Spring Security 3.1.4 avec la dépendance de configuration séparée Java, vers la nouvelle version 3.2.0 qui comprend Java. CSRF est activé par par défaut et je sais que je peux le désactiver dans ma méthode de configuration surchargée avec "http.csrf (). disable ()". Mais supposons que je ne veux pas le désactiver, mais j'ai besoin du jeton CSRF sur ma page de connexion où aucun JSP les bibliothèques de balises ou les bibliothèques de balises Spring sont utilisées.

Ma page de connexion est purement HTML que j'utilise dans une application Backbone que j'ai générée à l'aide de Yeoman. Comment dois-je procéder pour inclure le jeton CSRF contenu dans la session HttpSession sous la forme ou en tant qu'en-tête afin de ne pas obtenir le "jeton CSRF attendu introuvable. Votre session a-t-elle expiré?" exception?

23
Patrick Grimard

Vous pouvez obtenir le CSRF en utilisant l'attribut de demande nommé _csrf comme indiqué dans la référence . Pour ajouter le CSRF à une page HTML, vous devrez utiliser JavaScript pour obtenir le jeton qui doit être inclus dans les demandes.

Il est plus sûr de renvoyer le jeton en tant qu'en-tête que dans le corps en tant que JSON car JSON dans le corps peut être obtenu par des domaines externes. Par exemple, votre JavaScript peut demander une URL traitée par les éléments suivants:

CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// Spring Security will allow the Token to be included in this header name
response.setHeader("X-CSRF-HEADER", token.getHeaderName());
// Spring Security will allow the token to be included in this parameter name
response.setHeader("X-CSRF-PARAM", token.getParameterName());
// this is the value of the token to be included as either a header or an HTTP parameter
response.setHeader("X-CSRF-TOKEN", token.getToken());

Votre JavaScript obtiendrait alors le nom d'en-tête ou le nom de paramètre et le jeton de l'en-tête de réponse et l'ajouterait à la demande de connexion.

23
Rob Winch

Bien que @ rob-winch ait raison, je suggère de prendre un jeton de session. Si Spring-Security génère un nouveau jeton dans SessionManagementFilter à l'aide de CsrfAuthenticationStrategy, il le définira sur Session mais pas sur demande. Il est donc possible que vous vous retrouviez avec un mauvais jeton csrf.

public static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN");
CsrfToken sessionToken = (CsrfToken) request.getSession().getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);
4
bsmk

Remarque: J'utilise CORS et AngularJS.

Note²: J'ai trouvé Stateless Spring Security Part 1: Stateless CSRF protection = ce qui serait intéressant de conserver la façon dont AngularJS gère CSRF.

Au lieu d'utiliser Spring Security CSRF Filter qui est basé sur les réponses (en particulier celle de @Rob Winch), j'ai utilisé la méthode décrite dans La page de connexion: Angular = JS et Spring Security Part II .

En plus de cela, j'ai dû ajouter Access-Control-Allow-Headers: ..., X-CSRF-TOKEN (en raison de CORS).

En fait, je trouve cette méthode plus propre que l'ajout d'en-têtes à la réponse.

Voici le code:

HttpHeaderFilter.Java

@Component("httpHeaderFilter")
public class HttpHeaderFilter extends OncePerRequestFilter {
    @Autowired
    private List<HttpHeaderProvider> providerList;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        providerList.forEach(e -> e.filter(request, response));

        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
        }
        else {
            filterChain.doFilter(request, response);
        }
    }
}

HttpHeaderProvider.Java

public interface HttpHeaderProvider {
    void filter(HttpServletRequest request, HttpServletResponse response);
}

CsrfHttpHeaderProvider.Java

@Component
public class CsrfHttpHeaderProvider implements HttpHeaderProvider {
    @Override
    public void filter(HttpServletRequest request, HttpServletResponse response) {
        response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "X-CSRF-TOKEN");
    }
}

CsrfTokenFilter.Java

@Component("csrfTokenFilter")
public class CsrfTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CsrfToken csrf = (CsrfToken)request.getAttribute(CsrfToken.class.getName());

        if (csrf != null) {
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");

            String token = csrf.getToken();

            if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");

                response.addCookie(cookie);
            }
        }

        filterChain.doFilter(request, response);
    }
}

web.xml

...
<filter>
    <filter-name>httpHeaderFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>httpHeaderFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
...

security-context.xml

...
<custom-filter ref="csrfTokenFilter" after="CSRF_FILTER"/>
...

app.js

...
.run(['$http', '$cookies', function ($http, $cookies) {
    $http.defaults.transformResponse.unshift(function (data, headers) {
        var csrfToken = $cookies['XSRF-TOKEN'];

        if (!!csrfToken) {
            $http.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken;
        }

        return data;
    });
}]);
2