场景:
业务要求从把系统B嵌入到系统A中,系统A和系统B是完成不同的两个域名,前端同事完成系统嵌入后,从A系统内部调用B系统的接口时候发现跨域错误(CORS error),如下:
什么是跨域?
跨域(Cross Origin)指浏览器不允许当前页面所在的源去请求另一个源的数据,跨域也就是跨源的意思。
什么是同源?
同源策略(SOP Same origin policy):是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也是最基本的安全功能,如果缺少同源策略,浏览器很容易受到XSS、CSFR等攻击,所谓同源是指"协议+域名+端口"三者相同,必须满足这三个条件,才算做同源。
跨域具体场景举例:
当前页面 url 地址 | 被请求页面 url 地址 | 是否跨域 | 原因 |
---|---|---|---|
https://www.poratl.com | https://www.poratl.com/index | 否 | 协议+域名+端口 三者相同 |
https://www.poratl.com | http://www.poratl.com | 是 | 协议不同,http、https |
https://www.poratl.com | https://www.portal.com | 是 | 主域名不同 |
https://www.poratl.com | https://hy.portal.com | 是 | 子域名不同 |
https://www.poratl.com:10000 | https://www.poratl.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;/*** @ClassName: CorsFilter* @Author: zhangyong* @Date: 2024/4/8 17:35* @Description:*/
@Slf4j
@Component
@WebFilter(urlPatterns = { "/*" })
public class MyCorsFilter implements Filter {@Overridepublic 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,此处设置1hhttpServletResponse.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);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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;/*** @ClassName: CorsConfig* @Author: zhangyong* @Date: 2024/4/8 13:41* @Description:*/
@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;}@Beanpublic 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 {@Overridepublic 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)请求?
非简单请求时候会触发预检请求。
简单请求与非简单请求:
简单请求:
- 请求方法是 GET、HEAD、POST 中的一种。
- HTTP的头信息只能是 Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type 中的某几个,不能超出这个范围。
- Content-Type 的值只能是 text\plain、multipart/form-data、application/x-www-form-urlencoded 中的一种。
- 请求中没有使用 XMLHttpRequestUpload 对象。
- 请求中没有使用 ReadableStream 对象。
非简单请求:
简单请求的对立面就是非简单请求,也就是说不能同时满足简单请求条件的请求就是非简单请求,就可能会触发预检(OPTIONS)请求。