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); + } + +}