Skip to content

Spring Security - 8 解决跨域问题

本文主要介绍如何在Spring Boot中解决跨域问题,是Spring Security系列第八篇文章,前七篇文章如下:

关于跨域问题,详情请见文章:跨域问题解析

1. CrossOrigin注解

我们可以在类或接口方法上使用注解@CrossOrigin来解决跨域问题,例如:

java
@RestController
public class MapController {

    @GetMapping("/map1")
    @CrossOrigin(origins = {"http://127.0.0.1:5500"}, methods = {RequestMethod.GET})
    public Map<String, String> map1(){
        System.out.println("map1");
        Map<String, String> map = Map.of("a", "1", "b", "2","c","3");
        return map;
    }

    @GetMapping("/map2")
    public Map<String, String> map2(){
        System.out.println("map2");
        Map<String, String> map = Map.of("x", "-1", "y", "-2","z","-3");
        return map;
    }

}

注意:@CrossOrigin注解是Spring MVC提供的,所以不需要Spring Security支持。

然后编写网页:

html
<body>
    <button onclick="getMap1()">获取map1</button>
    <div id="map1"></div>

    <button onclick="getMap2()">获取map2</button>
    <div id="map2"></div>

    <script>
        async function getMap1() {
            const result = await fetch('http://127.0.0.1:8081/map1')
            const data = await result.json()

            document.getElementById('map1').innerHTML = JSON.stringify(data)
        }

        async function getMap2() {
            const result = await fetch('http://127.0.0.1:8081/map2')
            const data = await result.json()

            document.getElementById('map2').innerHTML = JSON.stringify(data)
        }
    </script>
</body>

然后启动该网页在http://127.0.0.1:5500地址上(通过Visual Studio插件Live Server实现),并点击两个按钮。结果如下:

image-20250112185431748

可以看到/map1由于配置了@CrossOrigin可以成功访问,/map2由于同源策略不能成功访问。

注意:这里严格来说不是说/map2没有被访问,而是返回值被浏览器拦截了。通过后台服务控制台的打印内容,我们可以看到map2被打印出来了:

image-20250112185750582

2.CorsConfigurationSource配置

结合Spring Security,我们可以通过CorsConfigurationSource来配置跨域策略:

java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Resource
    private DBUserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.csrf(csrfConfigurer -> csrfConfigurer.disable())
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(authorizeHttpRequestsConfigurer ->
                        authorizeHttpRequestsConfigurer
                                .requestMatchers(requestMatcher()).permitAll()
                                .anyRequest().authenticated())
                .build();
    }

    @Bean
    public RequestMatcher requestMatcher(){
        RequestMatcher requestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher("/login"),
                new AntPathRequestMatcher("/register"),
                new AntPathRequestMatcher("/error"),
                new AntPathRequestMatcher("/map1"),
                new AntPathRequestMatcher("/map2")
        );
        return requestMatcher;
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://127.0.0.1:5500")); // 允许的来源
        configuration.setAllowedMethods(Arrays.asList("GET","POST", "PUT", "DELETE")); // 允许的 HTTP 方法
        configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization")); // 允许的请求头
        configuration.setAllowCredentials(true); // 允许发送 Cookie 
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration); // 所有路径都应用此配置
        return source;
    }
}

这里详细说说configuration.setAllowCredentials(true)的含义。首先从客户端发起请求说起,需要设置是否包含凭证,这里设置为要包含:

javascript
fetch(url, {
  credentials: "include",
});
  • 对于简单请求,浏览器发送请求时会携带凭证,如果响应头不包括Access-Control-Allow-Credentials或该项值为false,那么浏览器会报错。

  • 对于跨域复杂请求,在发起正式请求之前会发送预检请求,预检请求不包含凭据。如果服务器对预检请求的响应将 Access-Control-Allow-Credentials 标头设置为 true,则真正的请求将包含凭据;否则浏览器会报网络错误。

而响应头:

properties
Access-Control-Allow-Credentials: true

就是通过configuration.setAllowCredentials(true)设置的。

另外,凭证是什么?

凭据包括 cookie、传输层安全 (TLS) 客户端证书或包含用户名和密码的身份验证标头。默认情况下,这些凭据不会在跨源请求中发送。

参考链接:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

问题:什么情况下可以发送跨域的Cookie?

有关的链接,还没有答案,先挖个坑:https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials

3. 总结

其实还有另一种方法,配置CorsFilter,但好像和第二种没有多大差别。

第一种和第二种的区别在于控制粒度的粗细,第一种控制到每个接口方法,第二种全局控制(当然也可以细粒度地控制)。一般情况下使用第二种方法就好。

跨域请求控制策略并不是Spring Security的内容,所以没有引入Spring Security框架也可以实现跨域请求控制:

https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html

在Spring Security中的有关跨域请求的文档: https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html