一、项目技术栈
SpringBoot + VUE2
二、服务器容器
Tomcat8 + Nginx
三、议题
受浏览器同源策略的影响 不同协议、不同主机[ IP、域名 ]、不同端口 间通信会发生跨域。
四、事件场景
1、生产环境:
前后端项目处于同一局域网下的内网环境中的不同主机及端口下
2、生产环境:
前后端项目处于同一物理服务器下的不同web容器下:
- SpringBoot项目处于Tomcat容器下;
- VUE项目处于Nginx容器下。
五、跨域问题的解决
1、生产环境:
SpringBoot项目
后端解决的原理就是在请求的响应头内添加若干允许请求通过跨域检查的属性:
@Configuration
public class ResourcesConfig implements WebMvcConfigurer{
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOrigin("*"); // 此处可以特异性的修改为你允许通过的主机,多个用","分隔
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 对接口配置跨域设置
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
VUE项目
前端项目解决的思路就是将该跨域的请求代理成前端自身所在主机的 Agreement ://IP:Port
后再向服务端发送
在vue-cil框架下搭建的项目 可配置 不同用途的环境,
在vue.config.js 脚手架 配置文件内:
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://192.168.0.100:8080`, // 后端开发接口地址
//ws: true,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
disableHostCheck: true
}
开发环境配置文件 .env.development:
# 开发环境配置
ENV = 'development'
# XX系统/开发环境
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true
当环境为开发环境时:面对axios异步请求时通过代理将该以'/dev-api'开头的请求代理到proxy:{}块内将target的后端接口 但实际的接口并无"/dev-api"此段上下文 ,所以通过pathRewrite:{}块进行地址重写将'/dev-api'替换为' ' ,从而实现api的还原, 然后请求到达服务端。 此段在npm 并部署 后 将不起作用
2、生产环境
SpringBoot项目
后端解决的思路仍旧就是在请求的响应头内添加若干允许请求通过跨域检查的属性:
@Configuration
public class ResourcesConfig implements WebMvcConfigurer{
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter()
{
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOrigin("*"); // 此处可以特异性的修改为你允许通过的主机,多个用","分隔
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 对接口配置跨域设置
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
VUE项目
前端项目解决的思路就是将该跨域的请求代理成前端自身所在主机的 Agreement ://IP:Port
后再向服务端发送, 但现在部署到web容器后 代理的操作只能寄希望于web容器 ,当前使用的是
Nginx容器
Nginx配置 [ nginx.conf ]:
server {
listen 8088;
listen [::]:8088;
server_name bi.xx.com; # 域名或IP
# 处理静态文件
location / {
root /usr/Prng/Bi/cilent/dist; # 静态文件的路径
index index.html index.htm;
try_files $uri $uri/ /index.html; # 处理单页面应用(SPA)
# 添加以下配置以允许跨域请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Allow-Credentials' 'true'; # 启用凭证支持
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Allow-Credentials' 'true'; # 启用凭证支持
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# add_header 'X-Debug-Location' "static-api-block"; # 添加自定义响应头
}
# 代理 API 请求
location ^~ /prod-api/ {
rewrite ^/prod-api/(.*)$ /$1 break; # 重写路径将 api 替换为空
proxy_pass http://bi.xx.com:8080/; # 当以/结尾时 也会将匹配的前缀/prod-api置空
# # 添加CORS头部
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
# add_header 'X-Debug-Location' "prod-api-block"; # 添加自定义响应头
#
# # 处理预检请求
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
return 204;
}
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;
# 自定义日志配置
# access_log /var/log/nginx/prod-api-access.log;
# error_log /var/log/nginx/prod-api-error.log;
}
}
比如一个实际请求要从8088向8080发送:http://bi.xx.com:8080/a/b/c ,但经过代理后请求流程变为
8088向自己的主机端口发送代前缀prod-api的请求 跨域检查通过 即当8088 产生请求http://bi.xx.com:8088/prod-api/a/b/c 当此请求到达nginx时 location ^~ /prod-api/ {}匹配到此请求
将前缀去除、将主机端口变成http://bi.xx.com:8080 ,完整的请求又变为http://bi.xx.com:8080/a/b/c 发送至服务端
六、location 匹配规则
在 Nginx 中,location
块用于定义不同的 URL 路径匹配规则,来处理不同的请求。Nginx 通过检查请求的 URI 来决定使用哪个 location
块。以下是 Nginx 中几种常见的 location
匹配规则及其优先级:
- 精确匹配 (
=
) - 前缀匹配 (
^~
) - 正则匹配 (
~
和~*
) - 普通前缀匹配(无特殊字符)
精确匹配 (=
)
location = /exact/path { ... }
:用于精确匹配请求的 URI。只有完全匹配的请求才会被处理。
location = /login {
# 处理精确匹配 /login 的请求
}
前缀匹配 (^~
)
location ^~ /prefix/ { ... }
:用于匹配以指定前缀开头的 URI,如果匹配成功,将立即停止搜索,并且不会继续检查正则表达式。
location ^~ /static/ {
# 处理以 /static/ 开头的所有请求
}
正则匹配 (~
和 ~*
)
location ~ /regex/ { ... }
:区分大小写的正则表达式匹配。location ~* /regex/ { ... }
:不区分大小写的正则表达式匹配。
location ~* \.(gif|jpg|jpeg)$ {
# 处理所有以 .gif、.jpg、.jpeg 结尾的请求(不区分大小写)
}
普通前缀匹配
location /prefix { ... }
:用于匹配以指定前缀开头的 URI,匹配过程将继续寻找其他匹配规则(正则匹配)。
location /images/ {
# 处理以 /images/ 开头的所有请求
}
匹配优先级
Nginx 按以下顺序处理请求:
- 精确匹配 (
=
)。 - 前缀匹配 (
^~
)。 - 正则匹配 (
~
和~*
),按文件中出现的顺序。 - 普通前缀匹配。
示例配置
假设前端项目在 Nginx 上的 8088 端口,后端项目在 Tomcat 的 8080 端口,并且后端接口没有 /api
前缀:
server {
listen 8088;
server_name your_domain.com;
# 配置前端项目
location / {
root /path/to/your/vue/project;
try_files $uri $uri/ /index.html;
}
# 配置反向代理,将 /api/ 请求转发到 Tomcat 服务器
location /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://localhost:8080/;
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;
# 添加CORS头
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Origin, Authorization, Content-Type, Accept';
}
# 处理OPTIONS预检请求
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Origin, Authorization, Content-Type, Accept';
return 204;
}
}
七、proxy_pass 结尾带不带"/"的区别
proxy_pass
指令的路径后面是否带有斜杠 (/
) 确实有区别。具体来说,proxy_pass http://localhost:8080/
和 proxy_pass http://localhost:8080
的行为在 URL 路径的重写和转发方面有所不同。
带斜杠的 proxy_pass
proxy_pass http://localhost:8080/;
-
如果
proxy_pass
后面的 URL 以斜杠结尾,Nginx 会将匹配的location
部分替换为proxy_pass
指令后面的路径。 -
例如:
location /api/ { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://localhost:8080/; }
如果请求是
http://your_domain.com/api/login
,它会被重写为http://localhost:8080/login
,并转发到后端服务器。
不带斜杠的 proxy_pass
proxy_pass http://localhost:8080;
-
如果
proxy_pass
后面的 URL 不以斜杠结尾,Nginx 不会将匹配的location
部分替换为proxy_pass
指令后面的路径,而是直接追加请求的 URI。 -
例如:
location /api/ { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://localhost:8080; }
如果请求是
http://your_domain.com/api/login
,重写后会是/login
,但最终转发的路径会是http://localhost:8080/api/login
,因为proxy_pass
后面没有斜杠。
总结
在你当前的场景中,假设你希望去掉 /api
前缀,并且正确转发请求路径,你应该使用带有斜杠的 proxy_pass