文章目录
- 一,跨域问题
- 1,跨域问题产生的原因
- 2,预检请求
- 3,跨域解决方案
- 3.1 CORS (Cross-Origin Resource Sharing)
- 后端配置示例(Spring Boot)
- 3.2 JSONP (JSON with Padding)
- 3.3 代理服务器
- Nginx代理配置示例
- 3.4 Server-Side Proxy (后端代理)
- 3.5 使用WebSocket
- 3.6 PostMessage API
- 3.7 CORS Anywhere
- 二,解决方案
一,跨域问题
当上一节设置好通过网关转发请求后,点击登录按钮,无法登录。
浏览器控制台有报错信息,如下。
图上的信息表明出现了跨域问题。
跨域问题是Web开发中常见的问题,尤其在前后端分离的架构中更为突出。
以“谷粒商城”为例,前端应用和后端服务部署分开部署,端口号不同。当前端应用尝试从后端API请求数据时,就会遇到跨域问题。
1,跨域问题产生的原因
浏览器出于安全考虑,实施了同源策略(Same-Origin Policy)。
根据同源策略,来自不同源(协议、域名、端口任意一项不同即视为不同源)的HTTP请求被限制,不能相互读取或写入对方的资源。
一定要协议、域名或者IP、端口都一致时,才不会被同源策略限制,可参考下面列表。
在上面的例子中,首先访问前端项目,端口是8001,打开登录界面后,点击登录,登录接口会发给网关,网关的端口是80,根据同源策略,端口不同不允许访问,会被浏览器限制访问。
2,预检请求
上面图片中的OPTIONS
,说明这个请求是个预检请求。
为什么需要预检请求呢?
原因是同源策略是浏览器的安全策略,在不同源的情况下,服务器端如果允许跨域的话,浏览器仍然可以发出跨域请求。
如果不同源,浏览器先发送一个轻量级的预检请求,询问服务器是否允许跨域访问,如果允许跨域访问,才会真正的发出请求。
上图图描述了非简单请求(如PUT、DELETE等)的跨域过程,具体来说,这是一个典型的CORS(Cross-Origin Resource Sharing)预检请求的过程。下面是详细的解释:
-
预检请求,OPTIONS:
浏览器发送一个预检请求(OPTIONS),询问服务器是否允许跨域请求。这个请求带有特殊的HTTP头,比如Origin
表示请求来源的URL,以及Access-Control-Request-Method
表示实际请求将使用的HTTP方法。 -
响应允许跨域:
服务器收到预检请求后,检查请求头,如果满足条件(如允许的源、方法、请求头等),则返回一个成功的响应,包含Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
等头,表明允许跨域请求。如果服务器不允许跨域,则返回一个失败的响应。 -
发送真实请求:
如果预检请求得到允许,浏览器才会发送真实的HTTP请求(如PUT或DELETE),携带实际的数据。 -
响应数据:
服务器处理完请求后,返回响应数据。此时,响应头中应包含Access-Control-Allow-Origin
等头,确认允许跨域。
这个过程是为了保护用户的安全,防止恶意网站窃取敏感数据。只有在预检请求得到允许后,浏览器才会发送真正的请求。对于简单请求(如GET、POST),浏览器可以直接发送,不需要预检请求。但对于非简单请求,浏览器必须先验证服务器是否允许跨域,以确保用户的隐私和安全。
3,跨域解决方案
只要浏览器允许跨域,浏览器就可以发出跨域请求。
解决跨域问题的常见方法有以下几种:
3.1 CORS (Cross-Origin Resource Sharing)
CORS 是一种最常用的跨域解决方案,允许服务器通过响应头来指定哪些源可以访问其资源。
服务器需要在响应中添加Access-Control-Allow-Origin
头来指明哪些源可以访问资源。
此外,还可以设置Access-Control-Allow-Methods
、Access-Control-Allow-Headers
、Access-Control-Allow-Credentials
等头来细化跨域访问的控制。
后端配置示例(Spring Boot)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 或指定的源
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600);
}
}
3.2 JSONP (JSON with Padding)
JSONP是一种用于绕过同源策略的技巧,但它仅限于GET请求。前端向服务器发送一个GET请求,服务器将数据包裹在回调函数中返回。这种方式的安全性较低,且不支持POST等其他HTTP方法。
3.3 代理服务器
通过设置一个代理服务器(如Nginx或Node.js的Express应用),前端向代理服务器发送请求,代理服务器再向实际的后端API发送请求。这样前端和后端看似在同一源下通信,绕过了同源策略的限制。
Nginx代理配置示例
location /api/ {
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
3.4 Server-Side Proxy (后端代理)
在后端API中添加逻辑,将请求转发到另一个源。这通常用于微服务架构中,一个服务充当其他服务的代理。
3.5 使用WebSocket
WebSocket协议不受同源策略的限制,可以用于建立持久的双向通信通道,适用于实时数据传输场景。
3.6 PostMessage API
HTML5引入的postMessage
API允许不同源的窗口之间进行安全通信,通常用于iframe之间的通信。
3.7 CORS Anywhere
CORS Anywhere是一个开源的Node.js中间件,可以作为代理服务器,自动添加CORS响应头,使得任何源都可以访问资源。但它可能涉及安全和隐私问题,应当谨慎使用。
我们采用第一种方案。
二,解决方案
考虑到所有的请求都会经过网关,所以可以在网关设置允许跨域,这样就不需要在每一个服务都进行跨域配置了。
我们通过创建一个配置类,在配置类中创建一个过滤器,过滤器给经过的所有请求的请求头加上允许跨域的信息。
配置类代码如下。
package com.atguigu.gulimall.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOriginPattern("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
经过这样设置之后,再次操作,就顺利登录了。