SpringBoot 跨域请求处理全攻略:从原理到实践

SpringBoot 如何处理跨域请求?你能说出几种方法?

在现代的Web开发中,跨域请求(Cross-Origin Resource Sharing,CORS)是一个常见的挑战。随着前后端分离架构的流行,前端应用通常运行在一个与后端 API 不同的域名或端口上,这就导致了浏览器的同源策略(Same-Origin Policy)的限制,从而出现了跨域请求问题。

Spring Boot 作为一种流行的 Java 后端框架,提供了多种处理跨域请求的方法,使得开发人员能够灵活地配置和管理跨域资源共享。本文将深入探讨几种常见的解决方案,帮助开发人员理解如何在 Spring Boot 应用中有效地处理跨域请求问题。

跨域请求概述

  1. 什么是跨域请求?
    跨域请求(Cross-Origin Request)指的是在浏览器环境下,前端代码发起的请求与当前页面的域名(或端口、协议)不同。浏览器的同源策略(Same-Origin Policy)限制了从一个源(域名、协议、端口组成的组合)加载的文档或脚本如何与来自另一个源的资源进行交互。

    具体来说,如果一个页面加载自 http://domain1.com,则它的同源策略默认限制了对 http://domain2.com 发起的请求。
    跨域问题是浏览器的一种安全策略,访问需要遵循同源策略:

URL说明是否允许通信
http://www.a.com/a.js http://www.a.com/b.js同一域名下允许
http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夹允许
http://www.a.com:8000/a.js http://www.a.com/b.js同一域名,不同端口不允许
http://www.a.com/a.js https://www.a.com/b.js同一域名,不同协议不允许
http://www.a.com/a.js http://192.168.110.11/b.js域名和域名对应ip不允许
http://www.a.com/a.js http://script.a.com/b.js主域相同,子域不同不允许
http://www.a.com/a.js http://a.com/b.js同一域名,不同二级域名(同上)不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js http://www.a.com/b.js不同域名不允许
  1. 为什么会出现跨域问题?

    跨域问题的出现主要是为了保护用户数据和用户隐私安全。如果没有同源策略的限制,恶意网站可以利用当前用户的身份在其他网站上进行操作,如发送请求、读取数据,这可能导致信息泄露或潜在的安全威胁。

  2. 可能会导致的安全风险

    • 信息泄露:允许恶意网站读取其他网站的敏感数据。
    • CSRF(跨站请求伪造)攻击:如果没有适当的防护措施,允许攻击者伪造用户的请求并在用户不知情的情况下执行操作。
    • 恶意脚本注入:通过跨域请求注入恶意脚本,影响其他域上的安全性。

跨域解决方案

1. 使用@CrossOrigin注解

Spring 框架提供了@CrossOrigin注解,可以直接在 Controller 类或方法上使用,以声明允许来自特定源的请求。例如:

@RestController
@RequestMapping("/api")
public class MyController {

    @CrossOrigin(origins = "http://allowed-origin.com")
    @GetMapping("/data")
    public ResponseEntity<String> getData() {
        // 处理逻辑
    }
}

优点:简单直接,适用于简单的跨域场景。

缺点:无法进行更细粒度的配置,如请求方法、请求头等。

2. 使用WebMvcConfigurer配置类

通过自定义WebMvcConfigurer配置类,可以更灵活地配置跨域请求。例如:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://allowed-origin.com")
                .allowedMethods("GET", "POST")
                .allowedHeaders("header1", "header2")
                .exposedHeaders("header3")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

优点:可以精确控制允许的源、方法、头部等。

缺点:需要编写额外的配置类,相对比较复杂。

3. 使用过滤器(Filter)

通过自定义过滤器来处理跨域请求,这种方法可以在请求到达 Controller 之前进行处理。例如:

@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        
        response.setHeader("Access-Control-Allow-Origin", "http://allowed-origin.com");
        response.setHeader("Access-Control-Allow-Methods", "GET,POST");
        response.setHeader("Access-Control-Allow-Headers", "header1,header2");
        response.setHeader("Access-Control-Expose-Headers", "header3");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Max-Age", "3600");
        
        chain.doFilter(req, res);
    }
}

优点:可以完全自定义跨域请求处理逻辑。

缺点:需要更多的Java编程经验,并且可能需要处理更复杂的跨域场景。
在使用Spring Security和Spring Cloud Gateway时,处理跨域请求(CORS)可以通过不同的方式实现。对于Spring Security,你可以在安全配置中添加CORS配置;而对于Spring Cloud Gateway,你可以使用全局过滤器或者特定的路由过滤器来处理CORS。

4. 使用Spring Security处理CORS

在Spring Security中,你可以在WebSecurityConfigurerAdapter的配置类中添加一个CorsConfigurationSource来指定CORS策略。但是,如果你使用的是Spring Security 5.3及以上版本,推荐使用WebSecurityCustomizer来配置CORS。

示例代码如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // ... your authorization rules here
            .anyRequest().authenticated();

        http.addFilterBefore(new JWTAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

5.使用Spring Cloud Gateway处理CORS

在Spring Cloud Gateway中,你可以在全局过滤器中添加CORS处理逻辑,或者在每个路由上单独配置CORS。这里是一个使用全局过滤器的例子:

@Configuration
public class CorsConfig {

    @Bean
    public GatewayFilter globalCorsFilter() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowCredentials(true);
        corsConfig.addAllowedOriginPattern("*");
        corsConfig.addAllowedHeader(HttpHeaders.AUTHORIZATION);
        corsConfig.addAllowedHeader(HttpHeaders.CONTENT_TYPE);
        corsConfig.addAllowedMethod(HttpMethod.GET.name());
        corsConfig.addAllowedMethod(HttpMethod.POST.name());
        corsConfig.addAllowedMethod(HttpMethod.PUT.name());
        corsConfig.addAllowedMethod(HttpMethod.DELETE.name());
        corsConfig.addAllowedMethod(HttpMethod.OPTIONS.name());

        List<String> allowedMethods = corsConfig.getAllowedMethods();
        corsConfig.setExposedHeaders(allowedMethods.stream().map(method -> "X-Permitted-Cross-Domain-Policies").collect(Collectors.toList()));

        CorsGatewayFilterFactory corsGatewayFilterFactory = new CorsGatewayFilterFactory(corsConfig);
        return corsGatewayFilterFactory.apply(new CorsConfig().new CorsConfigCustomizer());
    }

    public static class CorsConfigCustomizer implements CorsGatewayFilterFactory.CorsConfigurationCustomizer {
        @Override
        public void customize(CorsConfiguration corsConfiguration) {
            corsConfiguration.setMaxAge(3600L);
        }
    }
}

请注意,这些示例代码需要根据你的具体需求进行调整。例如,你可能需要限制允许的源,或者修改其他CORS设置。

补充

1. 预检请求(Preflight Requests)

跨域请求中,某些复杂请求(如带有自定义头部的请求、使用某些特殊方法如PUTDELETE等的请求)会触发浏览器先发送一个预检请求(OPTIONS请求)到服务器,以确定是否允许实际的请求。预检请求包含以下头部信息:

  • Origin:表明发起请求的源。
  • Access-Control-Request-Method:实际请求将使用的HTTP方法。
  • Access-Control-Request-Headers:实际请求将使用的自定义头部。

服务器需要正确响应预检请求,以确保浏览器安全地执行实际请求。
处理预检请求的方法:

  1. 使用@CrossOrigin注解处理

    	@CrossOrigin(origins = "http://allowed-origin.com", methods = {RequestMethod.GET, RequestMethod.POST}, allowedHeaders = {"header1", "header2"})
    	@RequestMapping(value = "/api/data", method = RequestMethod.OPTIONS)
    	public ResponseEntity<Void> preflight() {
    	    return ResponseEntity.ok().build();
    	}
    

    Controller中定义一个处理OPTIONS请求的方法,并使用@CrossOrigin注解指定允许的源、方法和头部。

  2. 通过WebMvcConfigurer配置类处理

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/api/**")
                    .allowedOrigins("http://allowed-origin.com")
                    .allowedMethods("GET", "POST")
                    .allowedHeaders("header1", "header2")
                    .exposedHeaders("header3")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/api/**")
                    .allowedOrigins("http://allowed-origin.com")
                    .allowedMethods("GET", "POST")
                    .allowedHeaders("header1", "header2")
                    .exposedHeaders("header3")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    }
    

    WebMvcConfigurer配置类中,通过重写addCorsMappings方法来定义预检请求的处理方式。

2. 其他注意事项

Credentials(凭证)的处理
如果请求需要使用凭证(如使用CookieHTTP认证信息等),需要确保在跨域请求中设置Access-Control-Allow-Credentialstrue,并在客户端请求中设置withCredentialstrue

暴露自定义头部
如果需要在响应中暴露某些自定义头部供客户端访问,可以通过Access-Control-Expose-Headers头部指定。

缓存控制
可以通过Access-Control-Max-Age头部指定预检请求的缓存时间,减少重复发送预检请求的次数,提升性能。

总结

避免跨域请求问题不仅仅是简单地允许跨域请求,还需要正确处理预检请求及相关细节。通过使用@CrossOrigin注解、自定义WebMvcConfigurer配置类或过滤器来处理跨域请求,可以有效地保证跨域请求的安全性和可靠性。同时,要特别注意凭证的处理及其他相关头部信息的配置,确保跨域请求能够在安全、可控的环境下完成。

思考

1. 如何区分简单请求和复杂请求?

跨域资源共享(CORS)规范将跨域请求分为两类:简单请求和预检请求(复杂请求)。区分这两类请求主要基于以下几个因素:

简单请求:

  • 方法:请求方法必须是以下之一:GET, HEAD, POST
  • 头信息:请求头中的字段不能包含除了Accept, Accept-Language, Content-Language, Last-Event-ID, Content-Type之外的自定义头部,且Content-Type的值只能是以下几种之一:application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain
  • 数据类型:如果是POST请求,发送的数据必须符合上述Content-Type的规定。

复杂请求(预检请求):

  • 如果请求不符合上述简单请求的标准,则被视为复杂请求。浏览器会先发送一个OPTIONS请求到服务器,这个请求称为预检请求,用于确认服务器是否支持跨域请求以及具体的请求方法和头部信息。

预检请求包含了实际请求的所有关键信息,包括请求方法、请求头部等,服务器通过预检请求响应告知客户端是否可以继续发送实际的请求。

2. 在实际项目中,如何选择合适的跨域请求解决方案?

选择跨域请求解决方案主要考虑以下几个方面:

  • 安全性:确保跨域访问不会导致安全漏洞,比如XSS攻击或CSRF攻击。
  • 性能:预检请求会增加额外的网络延迟,因此在设计API时应尽量避免不必要的复杂请求。
  • 功能需求:根据API的功能需求确定哪些HTTP方法和头信息是必要的,这将影响请求的类型。
  • 易用性:为开发者提供清晰的文档和示例,说明哪些资源可以被跨域访问,以及如何正确地设置请求头。

在Spring框架下,可以采用以下策略:

  • 使用Spring Security或Spring MVC的内置CORS支持,通过配置类来指定CORS策略。
  • 在Spring Cloud Gateway中,通过全局过滤器或路由级过滤器来处理CORS,这样可以更细粒度地控制跨域策略。

3. 如何处理凭证(Credentials)在跨域请求中的应用?

凭证通常指的是Cookie和HTTP认证信息(如Basic Auth),它们在跨域请求中默认是不会被发送的。如果需要发送凭证,需要在CORS策略中显式地允许:

  • 在服务器端,需要在CORS响应头中添加Access-Control-Allow-Credentials: true
  • 在客户端,当发起请求时,需要将withCredentials属性设置为true

示例代码:

服务端配置(Spring Security)

CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowCredentials(true); // 允许发送凭证

客户端调用(JavaScript/AJAX)

fetch('http://example.com/api', {
    method: 'GET',
    credentials: 'include', // 发送凭证
});

请注意,一旦允许了凭证的发送,所有跨域请求都必须遵循CORS策略,并且不能缓存预检请求的结果。此外,由于凭证的发送增加了安全风险,因此在配置时要格外小心,确保只对可信的源开放凭证权限。

Nginx 作为一款高性能的 HTTP 和反向代理服务器,可以用来处理跨域请求(CORS),关于Nginx相关的知识可以去看这篇文章:《Nginx——高性能Web服务器的基石,解锁Nginx的超级技能:从基础到实战的全方位指南》

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值