什么是跨域?又该怎么解决?
介绍
跨域,Cross Domain Request:从一个资源请求另一个资源,二者所在的请求地址不同,域名不同、端口号不同、请求协议不同。
思路一:Jsonp (JSON with Padding) ,是 json 的一种”使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。jsonp
跨域原理:src 属性不受到同源策略的限制。
更多解决方案,访问阮一峰的博客,或这篇博客
解决方法
解决跨域问题
- 前端,接收后端数据使用的数据类型改为jsonp
- 后端,设置响应头允许跨域
下边的解决方法是针对这个案例的修改:传送门。方法一与方法二的区别在前端使用的技术。一个是原生Ajax一个是jquery封装的Ajax。建议使用jquery。
更新
什么是跨域:跨域问题的出现是因为浏览器有一个同源策略限制。两个web资源(文件)同源是指它们的uri具有相同的协议(protocol),主机(host)和端口号(port)。同源策略能帮助阻隔恶意文档,减少可能被攻击的媒介。
解决跨域思路:
方法1-XHR
XMLHttpRequest(原生Ajax)可以向不同域名的服务器发出HTTP请求,叫做CORS。
前端修改
原来用于处理异步请求的JavaScript代码:
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
|
<script type="text/javascript"> function mysubmit(){ let user = document.getElementById("user"); let passwd = document.getElementById("passwd"); if (user.value.length != 0 && passwd.value.length != 0){ document.querySelector("h2").innerHTML="尝试发送数据到服务器!!!"; var xmlhttp; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) {
document.querySelector("h1").innerHTML=xmlhttp.responseText; document.querySelector("h2").innerHTML="收到服务器的响应!!!"; } } xmlhttp.open("POST","${pageContext.request.contextPath}/login",true); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send("user="+user.value+"&passwd="+passwd.value); } } </script>
|
在回调函数onreadystatechange里边添加下边的代码,处理返回的jsonp数据。open方法中的POST请求也需要改成GET请求。返回的数据是Document对象、JSON对象、普通字符串等:
1 2 3 4 5 6 7 8 9
|
var type=request.getResponseHeader("Content-Type"); if(type.indexOf("xml") !== -1 && request.responseXML){ callback(request.responseXML); }else if(type=== 'application/json'){ callback(JSON.parse(request.responseText)) }else { callback(request.responseText); }
|
如果返回的数据是图片流,可以参考这里进行数据处理。下边是一个使用demo:
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
|
function changecode(timeout){ let verificode_img = document.getElementById("verificode_img"); let xmlhttp; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { var blob = xmlhttp.response; verificode_img.onload = function() { window.URL.revokeObjectURL(verificode_img.src); }; verificode_img.src = window.URL.createObjectURL(blob); } } xmlhttp.open("GET",myuri+"/verificode?timeout="+timeout,true); xmlhttp.responseType = "blob"; xmlhttp.setRequestHeader("client_type", "DESKTOP_WEB"); xmlhttp.setRequestHeader("desktop_web_access_key", "_desktop_web_access_key"); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send(); }
|
后端修改
针对验证登陆模块案例的后端验证代码进行修改,修改如下:
1、设置响应头部,允许跨域。可以使用过滤器Filter进行配置。
1 2 3 4 5 6 7 8
|
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Allow-Credentials","true"); response.setHeader("Access-Control-Allow-Headers","*");
response.setCharacterEncoding("utf-8"); request.setCharacterEncoding("utf-8");
|
2、返回的数据类型为jsonp
1 2 3
|
String funString = req.getParameter("callback"); writer.write(funString + "("+o.toString()+")");
|
springBoot项目的配置,见后边笔记。
方法2-jq
上边的改进1中,只适用于get请求。登陆验证涉及到密码,所以不推荐使用。
前端修改
需要引入jquery,这里使用的是官方cdn。
1
|
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
将请求发起和响应处理函数由原来的mysubmit替换成下边的submit_data。data,从服务器返回的数据,status是返回的数据中的一个参数名。表单也进行了相应的修改:form表单添加了id=”loginForm“。两个输入框控件中id改成了name。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
function submit_data(){ var formParam = $("#loginForm").serialize(); $.ajax({ async:false, url:"http://qsdbl.site:8080/Login/login", type:"post", data:formParam, dataType:"jsonp", success: function(data){ console.log(data) if(data.status == true){ alert("登录成功"); $(location).attr('href', 'index.html'); } else alert("账号或者密码错误") }, error:function(error){ alert("登录失败") }
}); }
|
改进后的完整前端代码,见下边的demo1。
后端修改
同上。
![]()
方法3-vue-jsonp
axios处理jsonp数据,参考这篇博客。
引入vue和axios:
1 2
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
|
封装函数jsonp(放在scripts标签中vue变量前,不用放在vue内):
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
|
axios.defaults.baseURL = 'http://qsdbl.site:8080/myinterface';
axios.jsonp = (url, data) => { if (!url){ throw new Error('url is necessary') }else{ if (axios.defaults.baseURL != undefined){ url = axios.defaults.baseURL + url; } }
const callback = 'CALLBACK' + Math.random().toString().substr(9, 18) const JSONP = document.createElement('script') JSONP.setAttribute('type', 'text/javascript')
const headEle = document.getElementsByTagName('head')[0]
let ret = ''; if (data) { if (typeof data === 'string') ret = '&' + data; else if (typeof data === 'object') { for (let key in data) ret += '&' + key + '=' + encodeURIComponent(data[key]); } ret += '&_time=' + Date.now(); } JSONP.src = `${url}?callback=${callback}${ret}`; return new Promise((resolve, reject) => { window[callback] = r => { resolve(r) headEle.removeChild(JSONP) delete window[callback] } headEle.appendChild(JSONP) }) }
|
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
axios.jsonp(url, params) .then(res => console.log(res)) .catch(err => console.log(err))
axios.jsonp("/verificode", { timeout: this.timeout, uuid: this.uuid }).then(function(res) { vue.uuid = res.uuid; vue.vcodeImg = axios.defaults.baseURL + res.vpath; }).catch(err => console.log("axios请求出错,err:" + err))
|
应用案例,见这里:传送门。
方法4-springboot
新建一个类CorsConfig,实现接口WebMvcConfigurer。在该类中进行如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
package com.qsdbl.mypos.config;
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 { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET","HEAD", "POST", "DELETE", "PUT","PATCH","OPTIONS") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); } }
|
或者,web模块中,允许跨域请求的类使用注解“@CrossOrigin”标注。
![]()
方法5-vue-proxy
vue中使用proxy实现跨域:在vue项目根目录下新建vue.config.js
文件,添加如下配置
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
|
devServer: { proxy: { '/api': { target: 'http://47.xx.xx.xx:8081', changOrigin: true, pathRewrite: { '^/api': '' } }, '/api2': { target: 'http://36.xx.xx.xx:8086', changOrigin: true, pathRewrite: { '^/api2': '/userapi' } }, } }
|
浏览器中看到请求的资源为:
虚拟代理服务器实际访问的地址为:
demo1
改进后的前端代码:
这里已经不是使用jsp来写了,而是全部都使用HTML,所以要加上文档编码类型。见第五行。
后端代码已经修改,可以测试看看:http://www.qsdbl.site:8080/Login/login.html
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 39 40 41
|
<!DOCTYPE html> <html> <head> <title>login</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <body> <form id="loginForm" style="width: 500px;height: 200px" > <fieldset> <legend>登陆</legend> 用户名:<input type="text" name="user"><br> 密码:<input type="password" name="passwd"><br> <input type="button" value="提交" onclick="submit_data()" > </fieldset> </form> </body> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script> </head> <script type="text/javascript"> function submit_data(){ var formParam = $("#loginForm").serialize(); $.ajax({ async:false, url:"http://qsdbl.site:8080/Login/login", type:"post", data:formParam, dataType:"jsonp", success: function(data){ console.log(data) if(data.status == true){ alert("登录成功"); } else alert("账号或者密码错误"); }, error:function(error){ alert("登录失败"); } }); } </script> </html>
|
demo2
改进2的一个前端案例:
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
<!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>请登录</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <script> addEventListener("load", function () { setTimeout(hideURLbar, 0); }, false);
function hideURLbar() { window.scrollTo(0, 1); } </script>
<link rel="stylesheet" href="./请登录_files/style.css" type="text/css" media="all"> <link href="./请登录_files/font-awesome.min.css" rel="stylesheet"> </head>
<body> <h1> 科技战役 </h1>
<div class="container-agille"> <div class="formBox level-login"> <div class="box boxShaddow"></div> <div class="box loginBox"> <h3>登录</h3> <form class="form" id="loginForm"> <div class="f_row-2"> <input type="text" class="input-field" placeholder="账号" name="user" required=""> </div> <div class="f_row-2 last"> <input type="password" name="passwd" placeholder="密码" class="input-field" required=""> </div> <input class="submit-w3" type="button" value="登录" onclick="submit_data()"> <div class="f_link"> <a href="http://mrzhu11.host3v.com/login.html" class="resetTag">忘记密码</a> </div> </form> </div> <div class="box forgetbox agile"> <a href="http://mrzhu11.host3v.com/login.html#" class="back icon-back"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 199.404 199.404" style="enable-background:new 0 0 199.404 199.404;" xml:space="preserve"> <polygon points="199.404,81.529 74.742,81.529 127.987,28.285 99.701,0 0,99.702 99.701,199.404 127.987,171.119 74.742,117.876 199.404,117.876 "></polygon> </svg> </a> <h3>重置密码</h3> <form class="form" action="http://mrzhu11.host3v.com/login.html#" method="post"> <p>请输入你的注册邮箱</p> <div class="f_row last"> <label>邮箱</label> <input type="email" name="email" placeholder="Email" class="input-field" required=""> <u></u> </div> <button class="btn button submit-w3"> <span>确认</span> </button> </form> </div> <div class="box registerBox wthree"> <span class="reg_bg"></span> <h3>注册</h3> <form class="form" action="http://mrzhu11.host3v.com/register.php" method="post"> <div class="f_row-2"> <input type="text" class="input-field" placeholder="账号" name="name" required=""> </div> <div class="f_row-2 last"> <input type="password" name="password" placeholder="密码" id="password1" class="input-field" required=""> </div> <div class="f_row-2 last"> <input type="password" name="password" placeholder="再一次确认密码" id="password2" class="input-field" required=""> </div> <input class="submit-w3" type="submit" value="注册"> </form> </div> <a href="http://mrzhu11.host3v.com/login.html#" class="regTag icon-add"> <i class="fa fa-repeat" aria-hidden="true"></i>
</a> </div> </div>
<script src="./请登录_files/jquery-3.4.1.min.js"></script> <script src="./请登录_files/input-field.js"></script> <script> window.onload = function () { document.getElementById("password1").onchange = validatePassword; document.getElementById("password2").onchange = validatePassword; } function validatePassword() { var pass2 = document.getElementById("password2").value; var pass1 = document.getElementById("password1").value; if (pass1 != pass2) document.getElementById("password2").setCustomValidity("Passwords Don't Match"); else document.getElementById("password2").setCustomValidity(''); } function submit_data(){ var formParam = $("#loginForm").serialize(); $.ajax({ async:false, url:"http://qsdbl.site:8080/Login/login", type:"post", data:formParam, dataType:"jsonp", success: function(data){ console.log(data) if(data.status == true){ alert("登录成功"); $(location).attr('href', 'index.html'); } else alert("账号或者密码错误") }, error:function(error){ alert("登录失败") }
}); } </script>
</body></html>
|