0%

跨域问题

什么是跨域?又该怎么解决?

介绍

跨域,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)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
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
//xmlhttp.open("POST","${pageContext.request.contextPath}/login",true);//要改成GET请求
var type=request.getResponseHeader("Content-Type");
if(type.indexOf("xml") !== -1 && request.responseXML){
callback(request.responseXML); //Document对象响应
}else if(type=== 'application/json'){
callback(JSON.parse(request.responseText)) //JSON响应
}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)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
var blob = xmlhttp.response;//将返回的数据放到二进制变量blob
verificode_img.onload = function() {
window.URL.revokeObjectURL(verificode_img.src);
};//通过blob将图片流加载到img中,由于blob太大会影响性能,应该在加载之后及时释放
verificode_img.src = window.URL.createObjectURL(blob);//将验证码图片添加到html标签中
}
}
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");// 设置是否允许发送 cookies
response.setHeader("Access-Control-Allow-Headers","*");// *代表允许任何请求头字段
//顺便解决中文乱码
response.setCharacterEncoding("utf-8");
request.setCharacterEncoding("utf-8");

2、返回的数据类型为jsonp

1
2
3
//为了处理跨域问题,将json改成jsonp
String funString = req.getParameter("callback");//添加该语句(前端发送来的jsonp,默认会有callback参数)
writer.write(funString + "("+o.toString()+")");//修改改语句,将json包起来

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", //引号内为处理数据的代码地址,如果是php就是test.php
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';//设置全局URL
//封装函数jsonp,解决跨域请求
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))

//例如:
//使用封装的jsonp函数
axios.jsonp("/verificode", {
timeout: this.timeout,//"this."获取vue变量中的data数据
uuid: this.uuid
}).then(function(res) {
vue.uuid = res.uuid;//这里使用"vue."获取vue变量的值使用箭头函数则可以使用this(见上边用法示例)
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': {//配置1(可配置多个)
target: 'http://47.xx.xx.xx:8081',//要访问的资源地址
changOrigin: true, //允许跨域
pathRewrite: {
//重写路径,将/api替换成空字符(随意,当要注意替换后的资源路径正确)。可以理解为/api仅仅是起到一个标识的作用
'^/api': ''
}
},
'/api2': {//配置2
target: 'http://36.xx.xx.xx:8086',//要访问的资源地址
changOrigin: true, //允许跨域
pathRewrite: {
//重写路径,将/api替换成/userapi
'^/api2': '/userapi'
}
},
}
}
// “/api”、“/api2”下的资源访问,将会由代理服务器(虚拟)去获取,从而避开同源策略

//当发起请求时,顶层路径为/api,就会交由虚拟代理服务器去访问http://47.xx.xx.xx:8081下的资源
//当发起请求时,顶层路径为/api2,就会交由虚拟代理服务器去访问http://36.xx.xx.xx:8086/userapi下的资源

//tips: 若proxy中只配置了一个,则在创建axios实例的时候(一般在main.js中),可设置baseURL为/api

浏览器中看到请求的资源为:

虚拟代理服务器实际访问的地址为:

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>
<!-- saved from url=(0036)http://mrzhu11.host3v.com/login.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>
<!-- title -->
<h1>
科技战役
</h1>

<!-- content -->
<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", //引号内为处理数据的代码地址,如果是php就是test.php
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>
若图片不能正常显示,请在浏览器中打开

欢迎关注我的其它发布渠道