Disable CSRF token check for RESTful APIs (#2580)

#### 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 检查
```
pull/2587/head
John Niang 2022-10-17 16:03:38 +08:00 committed by GitHub
parent 2505c7fe4a
commit 2527eb42e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 28 deletions

View File

@ -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<SecurityConfigurer> 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) {

View File

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

View File

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