mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-20 19:12:14 +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
|
.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 {
|
final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
|
||||||
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
|
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);
|
return (StringUtils.hasText(headerValue) ? this.plain : this.xor).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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -860,31 +848,36 @@ class SecurityConfig {
|
|||||||
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() // <2>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) // <3>
|
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
class SpaCsrfTokenRequestHandler : CsrfTokenRequestHandler {
|
||||||
private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler()
|
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))) {
|
return if (StringUtils.hasText(headerValue)) {
|
||||||
super.resolveCsrfTokenValue(request, csrfToken)
|
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)
|
xor
|
||||||
}
|
}.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -918,21 +900,18 @@ XML::
|
|||||||
<csrf
|
<csrf
|
||||||
token-repository-ref="tokenRepository" <1>
|
token-repository-ref="tokenRepository" <1>
|
||||||
request-handler-ref="requestHandler"/> <2>
|
request-handler-ref="requestHandler"/> <2>
|
||||||
<custom-filter ref="csrfCookieFilter" after="BASIC_AUTH_FILTER"/> <3>
|
|
||||||
</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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user