首页 > 编程语言 >java service/web前端解决跨域( CORS policy: Response to preflight request doesn't pass access control

java service/web前端解决跨域( CORS policy: Response to preflight request doesn't pass access control

时间:2022-09-26 20:23:27浏览次数:114  
标签:control Control 跨域 No doesn Access ret Allow jsonp

1、什么是跨域?

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。

所谓同源是指,域名,协议,端口均相同,只要有一个不同,就是跨域。如:

http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

2、跨域会阻止什么操作?

浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询。

  • 1.阻止接口请求比较好理解,比如用ajax从http://192.168.100.150:8020/实验/jsonp.html页面向http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp发起请求,由于两个url端口不同,所以属于跨域,在console打印台会报No 'Access-Control-Allow-Origin' header is present on the requested resource

值得说的是虽然浏览器禁止用户对请求返回数据的显示和操作,但浏览器确实是去请求了,如果服务器没有做限制的话会返回数据的,在调试模式的network中可以看到返回状态为200,且可看到返回数据

  • 2.阻止dom获取和操作

比如a页面中嵌入了iframesrc为不同源的b页面,则在a中无法操作b中的dom,也没有办法改变b中dom中的css样式。

而如果ab是同源的话是可以获取并操作的。

<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style type="text/css">
        iframe {
            width: 100%;
            height: 800px;
        }
    </style>
</head>
<body>
    <!--<iframe src="http://192.168.100.150:8081/zhxZone/webmana/attachment/imageManager" frameborder="0" id="iframe"></iframe>-->
    <iframe src="http://192.168.100.150:8020/实验/jsonp.html" frameborder="0" id="iframe"></iframe>
    <script type="text/javascript">
        var i = document.getElementById("iframe");
        i.onload = function () {
            /*console.log(i.contentDocument)
            console.log(i.contentWindow.document.getElementById("text").innerHTML)*/
            var b = i.contentWindow.document.getElementsByTagName("body")[0];
            i.contentWindow.document.getElementById("text").style.background = "gray";
            i.contentWindow.document.getElementById("text").innerHTML = "111";
        }
    </script>
</body>
</html>

甚至是可以获取iframe中的cookie

var i=document.getElementById("iframe");
i.onload=function(){
	console.log(i.contentDocument.cookie);
}

不用说也知道这是极为危险的,所以浏览器才会阻止非同源操作dom

浏览器的这个限制虽然不能保证完全安全,但是会增加攻击的困难性

虽然安全机制挺好,可以抵御坏人入侵,但有时我们自己需要跨域请求接口数据或者操作自己的dom,也被浏览器阻止了,所以就需要跨域。

跨域的前提肯定是你和服务器是一伙的,你可以控制服务器返回的数据,否则跨域是无法完成的 。

一、服务器端

image-20220926135104644

image-20220926135135104

1.1、后端配置解决跨域 两种方式

CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否.

这其实和第2中方法(后台配置)基本相同,都是通过过滤器在response中返回头部,使服务器和浏览器可互通。

Access-Control-Allow-Origin:指定授权访问的域
Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)

f适合设置单一的(或全部)授权访问域,所有配置都是固定的,特简单。也没根据请求的类型做不同的处理

方式一:重写一个过滤器CORSFilter

@Component
public class CORSFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //只允许 http://localhost:9527 站点上的资源跨域
        //response.addHeader("Access-Control-Allow-Origin", "http://localhost:9527");
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        response.addHeader("Access-Control-Allow-Headers", "Content-Type");
        response.addHeader("Access-Control-Max-Age", "1800");//30 min
        filterChain.doFilter(request, response);
    }
}

image-20220926135310907

![image-20220926135343668](C:\Users\JourneyOfFlower\AppData\Roaming\Typora\typora-user-images\image-20220926135343668.png

image-20220926135357785

原因是请求中含有其他参数

image-20220926140342064

解决:放行所有原始域

response.addHeader("Access-Control-Allow-Headers", "Content-Type"); 改为 response.addHeader("Access-Control-Allow-Headers", "*");

 //response.addHeader("Access-Control-Allow-Headers", "Content-Type");
 response.addHeader("Access-Control-Allow-Headers", "*");

或者

加上 Authorizationuserid

 response.setHeader("Access-Control-Allow-Headers", "Authorization");
 response.setHeader("Access-Control-Allow-Headers", "userid");

方式二:springboot 配置注解

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
          //放行哪些原始域
          config.addAllowedOrigin("*");
          //是否发送Cookie信息
          config.setAllowCredentials(true);
          //放行哪些原始域(请求方式)
          config.addAllowedMethod("*");
          //放行哪些原始域(头部信息)
          config.addAllowedHeader("*");
          //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
          config.addExposedHeader("*");

        //2.添加映射路径
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        //允许 所有 站点上的资源跨域
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
}

1.2、通过Nginx反向代理

上个方法跨域是借助了浏览器对 Access-Control-Allow-Origin 的支持。但有些浏览器是不支持的,所以这并非是最佳方案,现在我们来利用nginx 通过反向代理 满足浏览器的同源策略实现跨域!

这里是一个nginx启用COSR的参考配置

#
# Wide-open CORS config for nginx
#
location / {
     if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        #
        # Tell client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
     }
}

跨域问题_炎升的博客

springboot 解决跨域- SegmentFault 思否

解决跨域请求的问题【服务器端】 - 知乎

二、web前端

1、方法:

1.1、前端方法就用jsonp

使用JSONP主要是目的通过动态创建Script,动态拼接url,进而抓取数据,实现跨域。这篇文章主要介绍了VUE2.0Jsonp的使用方法(前端),需要的朋友可以参考下

jsonp是前端解决跨域最实用的方法。

原理就是html中 的link,href,src属性都是不受跨域影响的,link可以调用远程的css文件,href可以链接到随便的url上,图片的src可以随意引用图片,script的src属性可以随意引入不同源的js文件

看下面代码,a.html页面中有一个func1方法,打印参数ret

<body>
    <script type="text/javascript">
        function func1(ret){
            console.log(ret)
        }
    </script>
    <script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp.js" type="text/javascript" charset="utf-8"></script>
</body>

而引入的jsonp.js中的代码为:

func1(111)

可想而知结果会打印出 111,也就是说a页面获取到了jsonp.js中的数据,数据是以调用方法并将数据放到参数中返回来的。

但是这样获取数据,必须a.html中的方法名与js中的引用方法名相同,这样就是麻烦很多,最好是a.html能将方法名动态的传给后台,后台返回的引入方法名就用我传给后台的方法名,这样就做到了由前台控制方法名。

总之要做到的就是前台像正常调接口一样,后台要返回回来js代码即可。

现在改为动态方法名:我请求的接口传入callback参数,值为方法名func1

<body>
	<script type="text/javascript">
		function func1(ret){
			console.log(ret)
		}
		
	</script>
	<script src="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1" type="text/javascript" charset="utf-8"></script>
</body>

后台返回不同编程语言不同,我使用的是java,所以我展示一下java返回的方法

//jsonp测试
@ResponseBody
@RequestMapping(value = "jsonp", produces = "text/plain;charset=UTF-8")
public void jsonp(String callback, HttpServletRequest req, HttpServletResponse res) {
    List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
    RetBase ret=new RetBase();
    try {
        res.setContentType("text/plain");
        res.setHeader("Pragma", "No-cache");
        res.setHeader("Cache-Control", "no-cache");
        res.setDateHeader("Expires", 0);
        Map<String,Object> params = new HashMap<String,Object>();
        list = dictService.getDictList(params);
        ret.setData(list);
        ret.setSuccess(true);
        ret.setMsg("获取成功");
        PrintWriter out = res.getWriter();
        //JSONObject resultJSON = JSONObject.fromObject(ret); //根据需要拼装json
        out.println(callback+"("+JSON.toJSONString(ret)+")");//返回jsonp格式数据
        out.flush();
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

到这里,每次请求数据还要引入一个js才行,代码有些杂乱,前端可以继续优化代码,动态的生成script标签。

<script type="text/javascript">
	function func1(ret){
		console.log(ret)
	}
	var url="http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=func1";
	var s=document.createElement("script");
	s.setAttribute("src",url);
	document.getElementsByTagName("head")[0].appendChild(s);	
</script>

这样,原生的jsonp跨域就基本完成了,但是用起来不是很方便。

推荐使用jquery封装的jsonp,使用起来相对简单。

使用起来跟使用ajax类似,只是dataType变成了jsonp,且增加了jsonp参数,参数就是上述的callback参数,不需要管他是啥值,因为jq自动给你起了个名字传到后台,并自动帮你生成回调函数并把数据取出来供success属性方法来调用

jqjsonp标准写法:

$.ajax({
    type: 'get',
    url: "http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp",
    dataType: 'jsonp',
    jsonp:"callback",
    async:true,
    data:{
        
    },
    success: function(ret){
        console.log(ret)
    },
    error:function(data) {
    },
});

jqjsonp的简便写法:

$.getJSON("http://192.168.100.150:8081/zhxZone/webmana/dict/jsonp?callback=?",function(ret){
    console.log(ret);
})

后台接收到的callback参数,jq自己起的名字

这样使用起来就跟ajax一样顺手了,把返回的值在success中操作即可,只不过是可以跨域了

这里针对ajaxjsonp的异同再做一些补充说明:

1、ajaxjsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jquery框架都把jsonp作为ajax的一种形式进行了封装。

2、但ajaxjsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加

1.2、 在vue中用法

VUE2.0中Jsonp的使用方法_vue.js_脚本之家 (jb51.net)

标签:control,Control,跨域,No,doesn,Access,ret,Allow,jsonp
From: https://www.cnblogs.com/JourneyOfFlower/p/16732251.html

相关文章