Browse Source

fix: apply CORS allowlist beneath servlet context path

integration/lxq-n03-n05-20261008
Zhang Shun 4 weeks ago
parent
commit
80969c65c3
  1. 3
      mydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.java
  2. 76
      mydreamjob-server/src/test/java/com/mydreamjob/config/CorsContextPathIntegrationTest.java

3
mydreamjob-server/src/main/java/com/mydreamjob/config/SecurityConfig.java

@ -181,7 +181,8 @@ public class SecurityConfig {
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
// 匹配的是去掉 servlet context-path 后的应用路径,来源仍由白名单限定。
source.registerCorsConfiguration("/**", config);
return source;
}

76
mydreamjob-server/src/test/java/com/mydreamjob/config/CorsContextPathIntegrationTest.java

@ -0,0 +1,76 @@
package com.mydreamjob.config;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(properties = {
"spring.datasource.url=jdbc:h2:mem:cors_context_path;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
"spring.jpa.show-sql=false",
"app.cors.allowed-origins=http://localhost:5173,http://localhost:3000"
})
@AutoConfigureMockMvc
@ActiveProfiles("integration-test")
class CorsContextPathIntegrationTest {
@Autowired MockMvc mvc;
@MockBean RedisTemplate<String, Object> redis;
@ParameterizedTest
@ValueSource(strings = {"http://localhost:5173", "http://localhost:3000"})
void configuredOriginCanPreflightIdentitySelectionWithApplicationContext(String origin) throws Exception {
mvc.perform(options("/api/v1/auth/identity/select")
.contextPath("/api/v1").servletPath("/auth/identity/select")
.header("Origin", origin)
.header("Access-Control-Request-Method", "POST")
.header("Access-Control-Request-Headers", "authorization,content-type"))
.andExpect(status().isOk())
.andExpect(header().string("Access-Control-Allow-Origin", origin))
.andExpect(header().string("Access-Control-Allow-Credentials", "true"))
.andExpect(header().string("Access-Control-Allow-Methods", containsString("POST")))
.andExpect(header().string("Access-Control-Allow-Headers", containsString("authorization")));
}
@ParameterizedTest
@ValueSource(strings = {"http://localhost:5173", "http://localhost:3000"})
void configuredOriginCanRead401WithoutBypassingAuthentication(String origin) throws Exception {
mvc.perform(get("/api/v1/auth/me")
.contextPath("/api/v1").servletPath("/auth/me")
.header("Origin", origin))
.andExpect(status().isUnauthorized())
.andExpect(header().string("Access-Control-Allow-Origin", origin));
}
@Test
void unconfiguredOriginRemainsRejected() throws Exception {
mvc.perform(options("/api/v1/auth/identity/select")
.contextPath("/api/v1").servletPath("/auth/identity/select")
.header("Origin", "https://untrusted.example")
.header("Access-Control-Request-Method", "POST"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Test
void unconfiguredHeaderRemainsRejected() throws Exception {
mvc.perform(options("/api/v1/auth/identity/select")
.contextPath("/api/v1").servletPath("/auth/identity/select")
.header("Origin", "http://localhost:5173")
.header("Access-Control-Request-Method", "POST")
.header("Access-Control-Request-Headers", "x-unapproved-header"))
.andExpect(status().isForbidden())
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
}
Loading…
Cancel
Save