|
@@ -788,14 +788,14 @@ public class SecurityConfig {
|
|
.csrf((csrf) -> csrf
|
|
.csrf((csrf) -> csrf
|
|
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1>
|
|
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1>
|
|
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2>
|
|
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2>
|
|
- )
|
|
|
|
- .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // <3>
|
|
|
|
|
|
+ );
|
|
return http.build();
|
|
return http.build();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
|
|
|
|
- private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
|
|
|
|
|
|
+final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
|
|
|
|
+ private final CsrfTokenRequestHandler plain = new CsrfTokenRequestAttributeHandler();
|
|
|
|
+ private final CsrfTokenRequestHandler xor = new XorCsrfTokenRequestAttributeHandler();
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
|
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
|
@@ -803,40 +803,28 @@ final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler
|
|
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
|
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
|
* the CsrfToken when it is rendered in the response body.
|
|
* the CsrfToken when it is rendered in the response body.
|
|
*/
|
|
*/
|
|
- this.delegate.handle(request, response, csrfToken);
|
|
|
|
|
|
+ this.xor.handle(request, response, csrfToken);
|
|
|
|
+ /*
|
|
|
|
+ * Render the token value to a cookie by causing the deferred token to be loaded.
|
|
|
|
+ */
|
|
|
|
+ csrfToken.get();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
|
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
|
|
|
+ String headerValue = request.getHeader(csrfToken.getHeaderName());
|
|
/*
|
|
/*
|
|
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
|
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
|
* to resolve the CsrfToken. This applies when a single-page application includes
|
|
* to resolve the CsrfToken. This applies when a single-page application includes
|
|
* the header value automatically, which was obtained via a cookie containing the
|
|
* the header value automatically, which was obtained via a cookie containing the
|
|
* raw CsrfToken.
|
|
* raw CsrfToken.
|
|
- */
|
|
|
|
- if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
|
|
|
|
- return super.resolveCsrfTokenValue(request, csrfToken);
|
|
|
|
- }
|
|
|
|
- /*
|
|
|
|
|
|
+ *
|
|
* In all other cases (e.g. if the request contains a request parameter), use
|
|
* In all other cases (e.g. if the request contains a request parameter), use
|
|
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
|
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
|
* when a server-side rendered form includes the _csrf request parameter as a
|
|
* when a server-side rendered form includes the _csrf request parameter as a
|
|
* hidden input.
|
|
* hidden input.
|
|
*/
|
|
*/
|
|
- return this.delegate.resolveCsrfTokenValue(request, csrfToken);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-final class CsrfCookieFilter extends OncePerRequestFilter {
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
|
|
- throws ServletException, IOException {
|
|
|
|
- CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
|
|
|
|
- // Render the token value to a cookie by causing the deferred token to be loaded
|
|
|
|
- csrfToken.getToken();
|
|
|
|
-
|
|
|
|
- filterChain.doFilter(request, response);
|
|
|
|
|
|
+ return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
----
|
|
@@ -856,35 +844,40 @@ class SecurityConfig {
|
|
http {
|
|
http {
|
|
// ...
|
|
// ...
|
|
csrf {
|
|
csrf {
|
|
- csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1>
|
|
|
|
- csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
|
|
|
|
|
+ csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() // <1>
|
|
|
|
+ csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) // <3>
|
|
|
|
return http.build()
|
|
return http.build()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
|
|
|
- private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
|
|
|
|
|
+class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler {
|
|
|
|
+ private val plain: CsrfTokenRequestHandler = CsrfTokenRequestAttributeHandler()
|
|
|
|
+ private val xor: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
|
|
|
|
|
override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
|
|
override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier<CsrfToken>) {
|
|
/*
|
|
/*
|
|
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
|
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
|
* the CsrfToken when it is rendered in the response body.
|
|
* the CsrfToken when it is rendered in the response body.
|
|
*/
|
|
*/
|
|
- delegate.handle(request, response, csrfToken)
|
|
|
|
|
|
+ xor.handle(request, response, csrfToken)
|
|
|
|
+ /*
|
|
|
|
+ * Render the token value to a cookie by causing the deferred token to be loaded.
|
|
|
|
+ */
|
|
|
|
+ csrfToken.get()
|
|
}
|
|
}
|
|
|
|
|
|
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
|
|
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
|
|
|
|
+ val headerValue = request.getHeader(csrfToken.headerName)
|
|
/*
|
|
/*
|
|
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
|
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
|
* to resolve the CsrfToken. This applies when a single-page application includes
|
|
* to resolve the CsrfToken. This applies when a single-page application includes
|
|
* the header value automatically, which was obtained via a cookie containing the
|
|
* the header value automatically, which was obtained via a cookie containing the
|
|
* raw CsrfToken.
|
|
* raw CsrfToken.
|
|
*/
|
|
*/
|
|
- return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) {
|
|
|
|
- super.resolveCsrfTokenValue(request, csrfToken)
|
|
|
|
|
|
+ return if (StringUtils.hasText(headerValue)) {
|
|
|
|
+ plain
|
|
} else {
|
|
} else {
|
|
/*
|
|
/*
|
|
* In all other cases (e.g. if the request contains a request parameter), use
|
|
* In all other cases (e.g. if the request contains a request parameter), use
|
|
@@ -892,19 +885,8 @@ class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
|
* when a server-side rendered form includes the _csrf request parameter as a
|
|
* when a server-side rendered form includes the _csrf request parameter as a
|
|
* hidden input.
|
|
* hidden input.
|
|
*/
|
|
*/
|
|
- delegate.resolveCsrfTokenValue(request, csrfToken)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-class CsrfCookieFilter : OncePerRequestFilter() {
|
|
|
|
-
|
|
|
|
- @Throws(ServletException::class, IOException::class)
|
|
|
|
- override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
|
|
|
- val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
|
|
|
- // Render the token value to a cookie by causing the deferred token to be loaded
|
|
|
|
- csrfToken.token
|
|
|
|
- filterChain.doFilter(request, response)
|
|
|
|
|
|
+ xor
|
|
|
|
+ }.resolveCsrfTokenValue(request, csrfToken)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
----
|
|
----
|
|
@@ -916,23 +898,20 @@ XML::
|
|
<http>
|
|
<http>
|
|
<!-- ... -->
|
|
<!-- ... -->
|
|
<csrf
|
|
<csrf
|
|
- token-repository-ref="tokenRepository" <1>
|
|
|
|
- request-handler-ref="requestHandler"/> <2>
|
|
|
|
- <custom-filter ref="csrfCookieFilter" after="BASIC_AUTH_FILTER"/> <3>
|
|
|
|
|
|
+ token-repository-ref="tokenRepository" <1>
|
|
|
|
+ request-handler-ref="requestHandler"/> <2>
|
|
</http>
|
|
</http>
|
|
<b:bean id="tokenRepository"
|
|
<b:bean id="tokenRepository"
|
|
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
|
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
|
p:cookieHttpOnly="false"/>
|
|
p:cookieHttpOnly="false"/>
|
|
<b:bean id="requestHandler"
|
|
<b:bean id="requestHandler"
|
|
class="example.SpaCsrfTokenRequestHandler"/>
|
|
class="example.SpaCsrfTokenRequestHandler"/>
|
|
-<b:bean id="csrfCookieFilter"
|
|
|
|
- class="example.CsrfCookieFilter"/>
|
|
|
|
----
|
|
----
|
|
======
|
|
======
|
|
|
|
|
|
<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application.
|
|
<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application.
|
|
<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`).
|
|
<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`).
|
|
-<3> Configure a custom `Filter` to load the `CsrfToken` on every request, which will return a new cookie if needed.
|
|
|
|
|
|
+ This implementation also causes the deferred `CsrfToken` to be loaded on every request, which will return a new cookie if needed.
|
|
|
|
|
|
[[csrf-integration-javascript-mpa]]
|
|
[[csrf-integration-javascript-mpa]]
|
|
==== Multi-Page Applications
|
|
==== Multi-Page Applications
|