Appearance
跨域的处理
产生原因
跨域产生的原因是由于前端地址与后台接口不是同源,从而导致 ajax 不能发送
非同源产生的问题
Cookie、LocalStorage 和 IndexDB 无法获取
DOM 无法获得
AJAX 请求不能发送
同源条件
协议,端口,主机 三者相同即为同源。反之,其中只要 某一个 不一样则为不同源
解决方式
本地开发跨域
本地开发一般使用下面 3 种方式进行处理
- vite 的 proxy 进行代理
- 后台开启 cors
- 使用 nginx 转发请求
生产环境跨域
生产环境一般使用下面 2 种方式进行处理
- 后台开启 cors
- 使用 nginx 转发请求
后台开启 cors 不需要前端做任何改动
vite 的 proxy 进行代理
如果你在 src/api/
下面的接口为下方代码,且 .env.development 文件配置如下注释,则在控制台看到的地址为 http://localhost:3100/api/oauth/anyTenant/login
。
由于
/api
匹配到了设置的VITE_PROXY
,所以上方实际是请求 **http://localhost:18760/api/oauth/anyTenant/login**,这样同时也解决了跨域问题。(**3100**为前端项目acuity-web-pro的端口号,**[http://localhost:18760](http://localhost:18760/)**为PROXY代理的目标后端acuity-gateway-server的地址)
// .env.development
// VITE_PROXY=[["/api","http://localhost:18760"]]
// VITE_GLOB_API_URL=/api
enum Api {
Login = '/oauth/anyTenant/login',
}
/**
* @description: 用户登陆
*/
export function loginApi(params: LoginParams) {
return http.request<LoginResultModel>({
url: Api.Login,
method: 'POST',
params,
});
}
// .env.development
// VITE_PROXY=[["/api","http://localhost:18760"]]
// VITE_GLOB_API_URL=/api
enum Api {
Login = '/oauth/anyTenant/login',
}
/**
* @description: 用户登陆
*/
export function loginApi(params: LoginParams) {
return http.request<LoginResultModel>({
url: Api.Login,
method: 'POST',
params,
});
}
#没有跨域时的配置
如果没有跨域问题,可以直接忽略 VITE_PROXY 配置,直接将接口地址设置在 VITE_GLOB_API_URL
# 例如接口地址为 http://localhost:18760 则
VITE_GLOB_API_URL=http://localhost:18760
# 例如接口地址为 http://localhost:18760 则
VITE_GLOB_API_URL=http://localhost:18760
如果有跨域问题,将 VITE_GLOB_API_URL 设置为跟 VITE_PROXY 内其中一个数组的第一个项一致的值即可。
下方的接口地址设置为 /api
,当请求发出的时候会经过 Vite 的 proxy 代理,匹配到了我们设置的 VITE_PROXY 规则,将 /api
转化为 http://localhost:18760
进行请求
# 例如接口地址为 http://localhost:18760 则
VITE_PROXY=[["/api","http://localhost:18760"]]
# 接口地址
VITE_GLOB_API_URL=/api
# 例如接口地址为 http://localhost:18760 则
VITE_PROXY=[["/api","http://localhost:18760"]]
# 接口地址
VITE_GLOB_API_URL=/api
#跨域原理解析
在 vite.config.ts
配置文件中,提供了 server 的 proxy 功能,用于代理 API 请求。
server: {
proxy: {
"/api":{
target: 'http://localhost:18760',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^/api`), ''),
}
},
},
server: {
proxy: {
"/api":{
target: 'http://localhost:18760',
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^/api`), ''),
}
},
},
注意:
从浏览器控制台的 Network 看,请求是
http://localhost:3100/api/xxx
,这是因为 proxy 配置不会改变本地请求的 url。
后台开启 cors之acuity-cloud
仅需在网关服务进行跨域配置即可:
java
@Configuration
public class CorsConfiguration {
private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Bean
@Order(Integer.MIN_VALUE)
public WebFilter corsFilter() {
return (ctx, chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
};
}
}
@Configuration
public class CorsConfiguration {
private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Bean
@Order(Integer.MIN_VALUE)
public WebFilter corsFilter() {
return (ctx, chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
};
}
}
后台开启 cors之acuity-boot
java
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final org.springframework.web.cors.CorsConfiguration config = new org.springframework.web.cors.CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOriginPattern("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
// 允许Get的请求类型
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final org.springframework.web.cors.CorsConfiguration config = new org.springframework.web.cors.CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOriginPattern("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
// 允许Get的请求类型
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
使用 nginx 转发请求
- 配置前端项目接口地址
ini
# 在.env.production内,配置接口地址
VITE_GLOB_API_URL=/api
# 在.env.production内,配置接口地址
VITE_GLOB_API_URL=/api
- 在 nginx 配置请求转发到后台
nginx
server {
listen 80;
server_name acuity.top;
# 接口代理,用于解决跨域问题
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后台 acuity-gateway-server 接口地址
proxy_pass http://110.110.1.1:8760/api;
proxy_redirect default;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
}
server {
listen 80;
server_name acuity.top;
# 接口代理,用于解决跨域问题
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后台 acuity-gateway-server 接口地址
proxy_pass http://110.110.1.1:8760/api;
proxy_redirect default;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
}