From 2527eb42e2235637b573659a6ccb37c9948e31f2 Mon Sep 17 00:00:00 2001 From: John Niang Date: Mon, 17 Oct 2022 16:03:38 +0800 Subject: [PATCH] Disable CSRF token check for RESTful APIs (#2580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind bug /area core /milestone 2.0 /kind api-change #### What this PR does / why we need it: 1. Disable CSRF token check for RESTful APIs but login and logout APIs. 2. Enable CORS check for login and logout APIs #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2571 #### How to test? 1. Install a valid theme and create a sample post 2. View the post at theme end 3. Check the response of counter API #### Does this PR introduce a user-facing change? ```release-note 禁用对 RESTful API 的 CSRF 检查 ``` --- .../app/config/WebServerSecurityConfig.java | 29 +--------------- .../run/halo/app/security/CorsConfigurer.java | 33 +++++++++++++++++++ .../run/halo/app/security/CsrfConfigurer.java | 27 +++++++++++++++ 3 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 src/main/java/run/halo/app/security/CorsConfigurer.java create mode 100644 src/main/java/run/halo/app/security/CsrfConfigurer.java diff --git a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java index a0ef29463..8f70740df 100644 --- a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java +++ b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java @@ -7,15 +7,12 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import java.util.Arrays; -import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.http.HttpHeaders; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; @@ -27,10 +24,6 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder; import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.reactive.CorsConfigurationSource; -import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.UserService; import run.halo.app.extension.ReactiveExtensionClient; @@ -63,10 +56,7 @@ public class WebServerSecurityConfig { RoleService roleService, ObjectProvider securityConfigurers) { - http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) - .and() - .cors(corsSpec -> corsSpec.configurationSource(apiCorsConfigurationSource())) - .securityMatcher(pathMatchers("/api/**", "/apis/**", "/login", "/logout")) + http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/login", "/logout")) .authorizeExchange(exchanges -> exchanges.anyExchange().access(new RequestInfoAuthorizationManager(roleService))) .anonymous(anonymousSpec -> { @@ -84,23 +74,6 @@ public class WebServerSecurityConfig { return http.build(); } - CorsConfigurationSource apiCorsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOriginPatterns(List.of("*")); - configuration.setAllowedHeaders( - List.of(HttpHeaders.AUTHORIZATION, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, - "X-XSRF-TOKEN", HttpHeaders.COOKIE)); - configuration.setAllowCredentials(true); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH")); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/api/**", configuration); - source.registerCorsConfiguration("/apis/**", configuration); - // TODO Remove both login and logout path until we provide the console proxy. - source.registerCorsConfiguration("/login", configuration); - source.registerCorsConfiguration("/logout", configuration); - return source; - } - @Bean ReactiveUserDetailsService userDetailsService(UserService userService, RoleService roleService) { diff --git a/src/main/java/run/halo/app/security/CorsConfigurer.java b/src/main/java/run/halo/app/security/CorsConfigurer.java new file mode 100644 index 000000000..8584fb228 --- /dev/null +++ b/src/main/java/run/halo/app/security/CorsConfigurer.java @@ -0,0 +1,33 @@ +package run.halo.app.security; + +import com.google.common.net.HttpHeaders; +import java.util.List; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.reactive.CorsConfigurationSource; +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; +import run.halo.app.security.authentication.SecurityConfigurer; + +@Component +public class CorsConfigurer implements SecurityConfigurer { + @Override + public void configure(ServerHttpSecurity http) { + http.cors(spec -> spec.configurationSource(apiCorsConfigSource())); + } + + CorsConfigurationSource apiCorsConfigSource() { + var configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(List.of("*")); + configuration.setAllowedHeaders( + List.of(HttpHeaders.AUTHORIZATION, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT, + "X-XSRF-TOKEN", HttpHeaders.COOKIE)); + configuration.setAllowCredentials(true); + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH")); + + var source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/api/**", configuration); + source.registerCorsConfiguration("/apis/**", configuration); + return source; + } +} diff --git a/src/main/java/run/halo/app/security/CsrfConfigurer.java b/src/main/java/run/halo/app/security/CsrfConfigurer.java new file mode 100644 index 000000000..6f2bf4cd4 --- /dev/null +++ b/src/main/java/run/halo/app/security/CsrfConfigurer.java @@ -0,0 +1,27 @@ +package run.halo.app.security; + +import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers; + +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository; +import org.springframework.security.web.server.csrf.CsrfWebFilter; +import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher; +import org.springframework.stereotype.Component; +import run.halo.app.security.authentication.SecurityConfigurer; + +@Component +public class CsrfConfigurer implements SecurityConfigurer { + + @Override + public void configure(ServerHttpSecurity http) { + var csrfMatcher = new AndServerWebExchangeMatcher( + CsrfWebFilter.DEFAULT_CSRF_MATCHER, + new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**") + )); + + http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) + .requireCsrfProtectionMatcher(csrfMatcher); + } + +}