mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-09 06:50:05 +00:00
Improve CSRF example for single-page apps
Closes gh-15105
This commit is contained in:
parent
17064fc7fb
commit
ee9f5a2d5e
@ -788,14 +788,14 @@ public class SecurityConfig {
|
||||
.csrf((csrf) -> csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // <1>
|
||||
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) // <2>
|
||||
)
|
||||
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); // <3>
|
||||
);
|
||||
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
|
||||
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
|
||||
* 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
|
||||
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||||
String headerValue = request.getHeader(csrfToken.getHeaderName());
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* 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
|
||||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||||
* when a server-side rendered form includes the _csrf request parameter as a
|
||||
* 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 {
|
||||
// ...
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
/*
|
||||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||||
* 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? {
|
||||
val headerValue = request.getHeader(csrfToken.headerName)
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* raw CsrfToken.
|
||||
*/
|
||||
return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) {
|
||||
super.resolveCsrfTokenValue(request, csrfToken)
|
||||
return if (StringUtils.hasText(headerValue)) {
|
||||
plain
|
||||
} else {
|
||||
/*
|
||||
* 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
|
||||
* 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>
|
||||
<!-- ... -->
|
||||
<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>
|
||||
<b:bean id="tokenRepository"
|
||||
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
||||
p:cookieHttpOnly="false"/>
|
||||
<b:bean id="requestHandler"
|
||||
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.
|
||||
<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]]
|
||||
==== Multi-Page Applications
|
||||
|
Loading…
x
Reference in New Issue
Block a user