瀏覽代碼

Improve CSRF example for single-page apps

Closes gh-15105
Steve Riesenberg 1 年之前
父節點
當前提交
ee9f5a2d5e
共有 1 個文件被更改,包括 30 次插入51 次删除
  1. 30 51
      docs/modules/ROOT/pages/servlet/exploits/csrf.adoc

+ 30 - 51
docs/modules/ROOT/pages/servlet/exploits/csrf.adoc

@@ -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