跨域问题是当前网站,通过ajax请求,访问其他网站资源会出现的问题,报错一般是报CORS相关内容,因为浏览器针对跨域请求,会默认走CORS解决方案,如果服务器没有对应的配置,就会报CORS相关的响应头Access-Control-Allow-Origin缺失异常
同源策略:
同源策略是浏览器的一种安全策略,简单来讲就是当前资源网站访问其他资源,浏览器就会进行判别,如果不是同源的资源,就会对本次请求返回的内容进行拦截,并在控制台抛出错误信息(注:浏览器只会对返回内容进行拦截,不会对请求拦截,也就是说从请求服务器,但服务器响应返回这个流程还会正常执行)。
浏览器判断是否是同源资源的方法:协议、域名、端口号,这三个如果有任何一个不同,都会判定为非同源资源。
如下情况会导致步骤二调用后端接口跨域问题
跨域问题解决方案
JSONP
早期的解决方式,但只能用于解决get请求,现在几乎不用,这里不做细化。
proxy
服务代理,此方式是在浏览器和所有的目标资源服务之间放一台代理服务器,如NGINX服务器,这样浏览器直接访问的都是NGINX服务,浏览器和NGINX不存在跨域了,至于通过NGINX转发后的地址不同,服务器之间不会有同源策略,自然就不会存在跨域问题。
注意:不要认为链路中有nginx就不存在跨域问题,必须全部的资源都用同一nginx做代理访问,如果出现下图中的访问index.html不采用代理,调用经过nginx代理的/test/getuserInfo也一样会出现跨域问题,因为index.html的h5服务和nginx服务域名不同。这种情况经常出现在前端开发在本地启动项目,然后访问测试环境接口进行调试。如果要满足这种使用情况,需单独在nginx配置CORS或者在后端服务上配置CORS
CORS
CORS是w3c的一个标准,全称是“跨域资源共享”(Cross-origin resource sharing)
CORS需要浏览器和服务器共同支持才能完成,目前,所有的主流浏览器都默认支持CORS,IE浏览器不能低于IE10;服务器需要自行设置才支持。
浏览器对跨域请求会进行特殊处理,加请求头Origin或者发一次options请求(预检请求),根据响应头中的信息判断服务器是否允许此次跨域请求。
nginx配置CORS
在配置文件中进行以下配置:主要是在对应的路由中添加 add_header 这些东西既可
server {
listen 80;
server_name localhost;
location /demo/ {
# 允许请求的域, *表示所有,一般生产上直接精确到允许访问的源地址
add_header 'Access-Control-Allow-Origin' *;
# 允许请求带上cookie
add_header 'Access-Control-Allow-Credentials' 'true';
# 允许请求的方法 比如get、post、head、delete、put等, *代表所有
add_header 'Access-Control-Allow-Metheds' *;
# 允许请求的header,一般根据你请求头里面的东西配置,*代表所有
add_header 'Access-Control-Allow-Headers' *;
proxy_pass http://localhost:8080/;
}
}
Java服务配置CORS
这里采用的springboot版本为2.7.11
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;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CORSConfig {
@Bean
public CorsFilter corsFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
List<String> param = new ArrayList<String>();
param.add("*");
// 设置是否允许携带cookie,当这个值为true时,AllowedOrigins的值不能设置为*
// corsConfiguration.setAllowCredentials(true);
// 设置允许的源,生产环境不建议设置为*,一般具体到对应的地址
corsConfiguration.setAllowedOrigins(param);
// 设置可以额外添加的请求头
corsConfiguration.setAllowedHeaders(param);
// 设置允许的请求方法
corsConfiguration.setAllowedMethods(param);
// 设置所有路径都拦截
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
CorsFilter corsFilter = new CorsFilter(source);
return corsFilter;
}
}
注意:nginx,gateway,具体微服务;这三个上面如果有多个地方配置了CORS,导致响应头中会存在多个相同key,会导致浏览器不知道应该选用哪个。删除其他CORS配置,只保留一个地方配置就行。(前提是跨域请求才会报错,正常请求即使返回了这些内容,也不会报错,因为浏览器压根就不会进行CORS验证)
CORS具体逻辑
浏览器将跨域请求分为简单请求和非简单请求,区分方法为
1、请求方式为 HEAD、GET、POST
2、请求头中没有添加自定义的请求头,只存在Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(里面的值只能为application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
符合以上两个条件即为简单请求,不符合即为非简单请求
CORS简单请求
浏览器判断跨域请求为简单请求的时候,会在请求头中添加Origin(表示源地址),服务器接收请求后,执行正常的接口处理逻辑,在响应的时候,如果允许跨域访问,一般即配置了CORS相关内容,那么会在响应头中添加一个Access-Control-Allow-Origin,表示请求允许的源地址,*表示允许所有地址,不允许跨域访问则没有这个响应头,浏览器拿到响应后,会判断响应头中是否存在Access-Control-Allow-Origin,如果存在,再判断值是否和当前源相同,否则报CORS相关跨域错误。
CORS非简单请求
浏览器判断当前请求为非简单跨域请求后,会以这个请求地址,发一个请求方式为options的请求(预检请求)
预检请求的请求头除了原有的内容,还添加了
Origin:源地址
Access-Control-Request-Method:之前请求的请求方式,不是本次预检请求的请求方式
Access-Conrol-Request-Headers:非简单请求多出的请求头字段
服务器收到请求后,检测这些字段后,确认允许此跨域请求后,就会给出回复
回复的响应头中包含
Access-Control-Allow-Origin:表示允许访问的源地址
Access-Control-Allow-Methods:表示允许访问的请求方法
Access-Control-Allow-Headers:表示允许额外添加的请求头
Access-Control-Allow-Creadentials:true,表示允许携带cookie
Access-Control-Allow-Max-Age:表示本次预检的有效期,有限期内,不会再发送预检请求
浏览器一旦通过了预检请求,在有效期内,浏览器进行此跨域访问,都会发简单请求,请求头中包含Origin,响应头中包含Access-Control-Allow-Origin