No ‘Access-Control-Allow-Origin‘ header is present跨域及解决
目录
- No ‘Access-Control-Allow-Origin‘ header is present
- http请求中,哪些是常见的简单请求,哪些是非简单请求
- 浏览器在发送跨域请求时候,会有哪些过程
- 跨域解决方案
- 总结
No ‘Access-Control-Allow-Origin‘ header is present
- 1 浏览器的限制
- 2 跨域
- 3 浏览器发送的是 XHR (XMLHttpRequest)请求
当以上三个条件都满足时浏览器会抛出跨域请求异常(记住是浏览器抛出的异常,和服务端没太大关系),在讲跨域请求解决方案前先了解几个问题。
http请求中,哪些是常见的简单请求,哪些是非简单请求
常见的简单请求:请求方法为:GET ,HEAD,POST,请求header里面无自定义头,Content-Type为以下几种:text/plain multipart/form-data application/x-www-form-urlencoded
常见的非简单请求 :请求方法为:put delete的AJAX请求,发送json格式的ajax请求,带自定义头的ajax请求
浏览器在发送跨域请求时候,会有哪些过程
如果是简单请求,浏览器会先发送请求,然后判断服务器返的返回头中是否支持跨域请求,否则抛出跨域异常
如果是非简单请求,浏览器会先发出OPTIONS请求方法的检测命令,判断服务器是否支持跨域请求,如果支持则发送真正的请求,如果不支持则抛出跨域异常,因此一个非简单请求每次会发送两个请求,后面跨域解决方案会讲到缓存OPTIONS预检请求
跨域解决方案
方案1:禁用浏览器跨域校验,即允许开发者_开发培训跨域访问,(这种方案不可取,不可能让所有的浏览器设置允许跨域访问)
谷歌浏览器禁用跨域校验: 创建一个快捷方式发送到桌面 ,快捷方式--》右键---》属性页面中的目标输入框里追加 --disable-web-security --user-data-dir=C:\Program Files (x86)\Google\Chrome\Application (注意:--user-data-dir的值就是浏览器安装目录。)不一定生效
方案2:采用jsonp方式,需要后台和前台同时改动代码,
1 前台需要设置callback参数,如果使用的是jquery ajax 那么dateType属性设置为jsonp,jquery框架会自动设置参数名为callback的请求参数,也可以通过jsonp属性修改jsonp请求参数名,其他js框架根据具体api使用,
2 后台接收到callback参数后认为是jsonp请求,需要返回jsonp格式,普通json请求返回的content-Type是application/json,而jsonp返回的是application/Javascript,同时也证明了jsonp请求服务端返回的是js脚本
3 jsonp请求参数名前后约定需要相同,例如jquery默认使用的是callback
弊端:jsonp 需要前后端都去修改代码,且jsonp是通过动态创建script脚本发送请求,仅支持 GET方法,jsonp发出的请求不是xhr请求,也是能解决跨域的原因
方案3:服务端解决跨域问题
通过编写filter在response对象中添加响应头,告诉浏览器允许跨域访问,* 号代码允许所有的请求域名,所有的请求方法跨域访问
@WebFilter("/*") public class CORSFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; // 告诉浏览器允许所有的域访问 // 注意 * 不能满足带有cookie的访问,Origin 必须是全匹配 // resp.addHeader("Access-Control-Allow-Origin", "*"); // 解决办法通过获取Origin请求头来动态设置 String origin = request.getHeader("Origin"); if (StringUtils.hasText(origin)) { resp.addHeader("Access-Control-Allow-Origin", origin); } // 允许带有cookie访问 resp.addHeader("Access-Control-Allow-Credenti编程客栈als", "true"); // 告诉浏览器允许跨域访问的方法 resp.addHeader("Access-Control-Allow-Methods", "*"); // 告诉浏览器允许带有Content-Type,header1,header2头的请求访问 // resp.addHeader("Access-Control-Allow-Headers", "Content-Type,header1,header2"); // 设置支持所有的自定义请求头 String headers = request.getHeader("Access-Control-RePTuUBICHhcquest-Headers"); if (StringUtils.hasText(headers)) { resp.addHeader("Access-Control-Allow-Headers", headers); } // 告诉浏览器缓存OPTIONS预检请求1小时,避免非简单请求每次发送预检请求,提升性能 resp.addHeader("Access-Control-Max-Age", "3600"); chain.doFilter(request, resp); } }
方案4:Spring框架提供了跨域解决方案
spring提供了 @CrossOrigin注解用户解决跨http://www.devze.com域问题,同时支持全局配置
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://domain2.com") .allowedMethods("PUT", "DELETE") .allowedHeaders("header1", "header2", "header3") .exposedHeaders("header1", "header2") .allowCredentials(false).maxAge(3600); } }
方案5 服务端通过ngnix解决跨域问题
location /{ proxy_pass http://localhost:8080/; #告诉浏览器允许跨域访问的方法 add_header Access-Control-Allow-Methods *; # 告诉浏览器缓存OPTIONS预检请求1小时 add_header Access-Control-Max-Age 3600; #允许带有cookie访问 add_header Access-Control-Allow-Credentials trhttp://www.devze.comue; #注意编程客栈 * 不能满足带有cookie的访问,Origin 必须是全匹配,这里通过变量获取 add_header Access-Control-Allow-Origin $http_origin; #设置支持所有的自定义请求头 add_header Access-Control-Allow-Headers $http_access_control_request_headers; #如果预检请求,则返回成功,不需要转发到后端 if ($request_method = OPTIONS){ return 200; } }
方案6 客户端通过nginx隐藏跨域
#转发全部以/api开头的请求到web服务器 location/api { proxy_passhttp://127.0.0.1:8080/api; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。
精彩评论