场景:
业务要求从把系统B嵌入到系统A中,系统A和系统B是完成不同的两个域名,前端同事完成系统嵌入后,从A系统内部调用B系统的接口时候发现跨域错误(CORS error),如下:
什么是跨域?
跨域(Cross Origin)指浏览器不允许当前页面所在的源去请求另一个源的数据,跨域也就是跨源的意思。
什么是同源?
同源策略(SOP Same origin policy):是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也是最基本的安全功能,如果缺少同源策略,浏览器很容易受到XSS、CSFR等攻击,所谓同源是指"协议+域名+端口"三者相同,必须满足这三个条件,才算做同源。
跨域具体场景举例:
当前页面 url 地址 | 被请求页面 url 地址 | 是否跨域 | 原因 |
---|---|---|---|
https://www.aaa.com | https://www.aaa.com/index | 否 | 协议+域名+端口 三者相同 |
https://www.aaa.com | http://www.aaa.com | 是 | 协议不同,http、https |
https://www.aaa.com | https://www.aaa.com | 是 | 主域名不同 |
https://www.aaa.com | https://hy.aaa.com | 是 | 子域名不同 |
https://www.aaa.com:10000 | https://www.aaa.com:10010 | 是 | 端口不同 |
什么原因导致浏览器报跨域错误?
发起ajax请求的那个页面的地址 和 ajax接口地址 不在同一个域中,直接导致了跨域问题,也就是说跨域问题发生在浏览器。
跨域问题解决方案:
Nginx 反向代理解决跨域
Nginx 反向代理解决跨域,只需要在 nginx 上增加配置文件,即可解决跨域问题,如下:
server {
listen 80;
listen 443;
server_name xxx.test.com;
root /usr/share/nginx/html;
index index.html index.htm;
#跨域配置
add_header Access-Control-Allow-Methods GET,POST,PUT,OPTIONS,DELETE,PATCH;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers *;
#access_log /data/logs/nginx/nginx_elk_log/test.com.access.log nginx-json-log;
#error_log /data/logs/nginx/nginx_elk_log/test.com.error.log;
if ($request_method !~* GET|POST|HEAD) {
return 403;
}
location / {
#跨域配置(和上面的跨域配置二选一即可)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers *;
add_header_Access-Control-Allow-MethodS GET,PUT,DELETE,POST,OPTIONS;
#另外一种配置方式 预检请求直接返回 否则可能代码中有鉴权过不去 还要额外处理
if ($request_method = 'OPTIONS'){
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS;
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
return 204;
}
#root /data/web/html/zteam;
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ^~ /WEB-INF{
deny all;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
Nginx 增加配置解决跨域问题,只使用一种解决问题即可,不要同时配置多个。
Nginx 知识传送门:
Nginx 故障排查之斜杠(/) --(附 Nginx 常用命令)
服务端解决跨域问题
解决 CORS 跨域问题,就是在服务器端给响应添加头信息,解释如下:
Access-Control-Allow-Origin 允许请求的域
Access-Control-Allow-Methods 允许请求的方法
Access-Control-Allow-Headers 预检请求后,告知发送请求需要有的头部
Access-Control-Allow-Credentials 表示是否允许发送cookie,默认false;
Access-Control-Max-Age 本次预检的有效期,单位:秒;
1、使用过滤器解决跨域问题,注意该方案需要在启动类加注解:@ServletComponentScan({“com.my.study.main.filter”}
package com.my.study.main.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
@WebFilter(urlPatterns = { "/*" })
public class MyCorsFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
log.info("进入了过滤器,请求路径为:{}",request.getRequestURL());
HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
// 允许跨域的域名,*:代表所有域名
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
// 允许跨域请求的方法
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
// 本次许可的有效时间,单位秒,过期之前的ajax请求就无需再次进行预检啦
// 默认是1800s,此处设置1h
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
// 允许的响应头
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
// 支持HTTP 1.1.
httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
// 支持HTTP 1.0. response.setHeader("Expires", "0");
httpServletResponse.setHeader("Pragma", "no-cache");
// 编码
httpServletResponse.setCharacterEncoding("UTF-8");
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
2、过滤器解决跨域的另外一种实现方式。
package com.my.study.main.configurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
2、添加 @Configuration 注解,实现 WebMvcConfigurer 接口,解决跨域问题。
package com.my.study.main.configurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowCredentials(true) // 是否发送 Cookie
.allowedOriginPatterns("*") // 支持域
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支持方法
.allowedHeaders("*")
.exposedHeaders("*");
}
跨域解决方案总结:
推荐使用 Nginx 处理跨域问题,只需要在nginx 上增加配置即可解决问题,而服务端解决跨域问题,或多或少都需要写代码,本着少改代码的原则,强烈建议使用 Nginx 的方式解决跨域问题,不管使用哪种方式解决跨域问题,只需要使用一种即可,不要多种方式叠加使用。
温馨提示:
跨域问题通常是伴随多个系统一起出现了,也就是出现了跨系统调用,可能会出现跨域问题,这个时候要主要多个系统的权限认证是否通用,如果权限认证不通用,要优先解决权限认证的问题,否则也是提示跨域问题,浏览器端常见错误如下:
什么是预检(OPTIONS)请求?
浏览器使用 OPTIONS 方法发起一个预检请求(preflight request),来感知服务端是否允许该跨域请求,服务器确认允许之后,才发起实际的 HTTP 请求,OPTIONS 请求没有附带请求数据,响应体也为空,简单来说就是一种探测,这就是预检请求,是浏览器的一种保护机制。
预检(OPTIONS)请求的作用?
什么时候会触发预检(OPTIONS)请求?
非简单请求时候会触发预检请求。
简单请求与非简单请求:
简单请求:
非简单请求:
简单请求的对立面就是非简单请求,也就是说不能同时满足简单请求条件的请求就是非简单请求,就可能会触发预检(OPTIONS)请求。
如有错误的地方欢迎指出纠正。