跨域问题总结 什么是跨域 为什么会跨域
跨域问题一直是前端的一大难题,从前端出道到至今,无论是自己还是身边的同事,以及网上前端朋友都被这个问题困扰着。
贴上标准的前端跨域报错:
Access to XMLHttpRequest at 'http://127.0.0.1:8080/cors_backend_war_exploded/test' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
说到跨域不得不谈的就是浏览器的同源策略 ,跨域也是因为浏览器这个机制引起的,这个机制的存在还是在于安全。
什么是源
Web内容的源由用于访问它的URL 的方案(协议),主机(域名)和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。
同源不同源一句话就可以判断:就是url中 scheme host port 都相同即为同源。
下面认识下url 结构中的这三个部分。
url结构
URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。
URL有如下结构组成:
Schme 或者 Protocol
Domain Name 也叫做host域名
port 端口号
Parameters参数
Anchor 锚点,一般用于定位位置
浏览器为什么需要同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
前端解决方案 jsonp解决 我们发现Web页面上调用js文件时则不受是否跨域的影响,拥有”src”这个属性的标签都却拥有跨域的能力,比如<\a>
、<\script>
、<\img>
、<\iframe>
。那么跨域访问数据就有了一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理。就好比使用一个<script>
,让其src属性指向我们要访问的跨域资源,然后以接收js文件的形式接收数据。
JSONP的工作原理是通过动态向页面添加一个脚本标记,该标记的src属性指向远程JSONP API的URL。服务器返回的JSONP脚本被包装在一个函数调用中,当脚本下载完成后,该函数将自动执行。这就是为什么我们需要告诉JSONP要将脚本包装在哪个回调函数中的原因。这个函数在脚本下载后将被调用。JSONP的安全性问题主要在于脚本可能控制页面并对用户造成风险。因此,JSONP并不安全,只是没有被Web浏览器阻止而已。使用JSONP需要谨慎。需要注意的是jsonp
只能用于get
请求。
前端代码
ajax中设置参数dataType:"jsonp",
表示这个请求是个jsonp
请求。设置jsonpCallback:"callback",
可以设置我们自定义返回调用的函数名。请求结束后,调用的函数其实就是我们的success
,后端传入的参数也可以接受。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <%@page contentType="text/html;charset=UTF-8" %> <html> <head> <script src="js/jquery.min.js" ></script> <script> let f1 = () => { console.log("12123" ); $.ajax({ url:"http://127.0.0.1:8080/cors_backend_war_exploded/test" , type:"get" , dataType:"jsonp" , jsonpCallback:"callback" , success(res){ console.log(res); } } ); }; </script> </head> <body> <h2>Hello World!</h2> </body> <a href="http://127.0.0.1:8080/cors_backend_war_exploded/test" >跨域a标签请求</a> <button id="cors-button" onclick="f1()" >跨域ajax请求</button> </html>
后端代码
在后端只需要注意把返回数据设置js
文件,并且返回的是一个函数调用,函数名需要和前端设置的名称一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.zgy.cors.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Controller public class MyController { @RequestMapping("/test") public void testCors (HttpServletResponse response) throws IOException { response.setContentType("text/javaScript;charset=UTF-8" ); response.getWriter().print("callback(123222)" ); } }
后端解决方案
CORS的实现方式主要是通过HTTP头部来实现的,浏览器会在请求中添加一些自定义的HTTP头部,告诉服务器请求的来源、目标地址等信息。服务器在接收到请求后,会根据请求头中的信息来判断是否允许跨域请求,并在响应头中添加一些自定义的HTTP头部,告诉浏览器是否允许请求、允许哪些HTTP方法、允许哪些HTTP头部等信息。
在响应头中添加以下字段,可以解决跨域问题:
access-control-allow-origin
: 该字段是必须的。它的值要么是请求时 Origin
字段的值,要么是一个 *
,表示接受任意域名的请求。
access-control-allow-credentials
: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为 true
,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为 true
,如果服务器不要浏览器发送Cookie,删除该字段即可
Access-Control-Allow-Methods
: 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
其实最重要的就是 access-control-allow-origin
字段,添加一个 * ,允许所有的域都能访问
@CrossOrigin
注解该注解可以作用在类以及方法层面上,当作用在类(Controller)层面上时,该类下的所有路径均允许跨域访问;当作用在方法层面上时,只有当前方法对应的路径允许跨域访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.zgy.cors.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Controller @CrossOrigin public class MyController { @RequestMapping("/test") public void testCors (HttpServletResponse response) throws IOException { System.out.println("123123" ); } }
配置过滤器 使用过滤器我们可以对请求和响应头进行设置,做到跨域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.zgy.cors.filter;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Component public class MyFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { } @Override public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; String origin = request.getHeader("Origin" ); if (origin!=null ) { response.setHeader("Access-Control-Allow-Origin" , origin); } String headers = request.getHeader("Access-Control-Request-Headers" ); if (headers!=null ) { response.setHeader("Access-Control-Allow-Headers" , headers); response.setHeader("Access-Control-Expose-Headers" , headers); } response.setHeader("Access-Control-Allow-Methods" , "*" ); response.setHeader("Access-Control-Max-Age" , "3600" ); response.setHeader("Access-Control-Allow-Credentials" , "true" ); chain.doFilter(request, response); } @Override public void destroy () { } }
web.xml
1 2 3 4 5 6 7 8 <filter > <filter-name > myFilter</filter-name > <filter-class > com.zgy.cors.filter.MyFilter</filter-class > </filter > <filter-mapping > <filter-name > myFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
这个方法在非springboot环境下需要加@EnableWebMvc
注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/**" ) .allowedOriginPatterns("*" ) .allowCredentials(true ) .allowedMethods("POST" ,"PUT" ,"DELETE" ,"GET" ) .allowedHeaders("*" ) .maxAge(3600 ); } }
需要注意,如果配置了SpringSecurity,需要在SpringSecurity的配置类里面加入http.cors()
,表示我们SpringSecurity允许跨域请求。