如何解決跨域問題?
如何解決跨域問題?首先我們需要知道什麼是跨域,跨域指的是浏覽器不能執行其它網站的腳本,它是由浏覽器的同源政策造成的,是浏覽器對JavaScript 施加的安全限制。
1、同源政策
根據百度百科 同源政策它是由 Netscape 提出的一個安全政策,它是浏覽器最核心也是最基本的安全功能,如果缺少同源政策,則浏覽器的正常功能可能都會受到影響,現在所有支援JavaScript的浏覽器都會使用這個政策。
所謂同源指的是:
協定、域名、端口号都相同,隻要有一個不相同,那麼都是非同源。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SMjRzYmRGOzUmY2IjZzYWZ4Q2N2EjZ0MTZ2UTMlJjY58CX4IzLcRDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.png)
浏覽器在執行腳本的時候,都會檢查這個腳本屬于哪個頁面,即檢查是否同源,隻有同源的腳本才會被執行;而非同源的腳本在請求資料的時候,浏覽器會報一個異常,提示拒絕通路。
①、http://www.123.com/index.html 調用 http://www.123.com/welcome.jsp 協定、域名、端口号都相同,同源。
②、https://www.123.com/index.html 調用 http://www.123.com/welcome.jsp 協定不同,非同源。
③、http://www.123.com:8080/index.html 調用 http://www.123.com:8081/welcome.jsp 端口不同,非同源。
④、http://www.123.com/index.html 調用 http://www.456.com/welcome.jsp 域名不同,非同源。
⑤、http://localhost:8080/index.html 調用 http://127.0.0.1:8080/welcom.jsp 雖然localhost等同于 127.0.0.1 但是也是非同源的。
同源政策限制的情況:
1、Cookie、LocalStorage 和 IndexDB 無法讀取
2、DOM 和 Js對象無法獲得
3、AJAX 請求不能發送
注意:對于像 img、iframe、script 等标簽的 src 屬性是特例,它們是可以通路非同源網站的資源的。
2、跨域執行個體示範
我們建立了兩個 web 項目JavaWeb01 和 JavaWeb02 分别部署在tomcat1和Tomcat2上上,這兩個 Tomcat 的端口号設定是不一樣的,一個是 8080,一個是8081,是以這兩個項目構成了非同源。那麼我們從用戶端(浏覽器)輸入通路部署在 Tomcat2 上的項目 JavaWeb2,然後在該項目中通過 ajax 去請求部署在 Tomcat1 上的項目資料,能夠通路的到呢?
①、在 JavaWeb02 項目中,有一個 jsp 檔案,我們通過在浏覽器通路該 JSP 檔案去擷取 JavaWeb01 項目中的資料
1 <%@ page language="java" contentType="text/html; charset=UTF-8"
2 pageEncoding="UTF-8" isELIgnored="false"%>
3 <%
4 String path = request.getContextPath();
5 String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
6 + path;
7 %>
8 <!DOCTYPE html>
9 <head>
10 <title>Title</title>
11 </head>
12 <script type="text/javascript" src="<%=basePath%>/js/jquery-3.3.1.min.js"></script>
13 <script type="text/javascript">
14 $(document).ready(function(){
15 $.ajax({
16 type:"get",
17 async:false,
18 url:"http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName=Tom",
19 dataType:"json",
20 success:function (data) {
21 alert(data['passWord']);
22 },
23 error:function () {
24 alert("error");
25 }
26
27 });
28 })
29
30 </script>
31 <body>
32
33 </body>
34 </html>
View Code
通過ajax 通路
url:"http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName=Tom"
去擷取 JavaWeb01 項目中的資料。
②、在 JavaWeb01 項目中,建立一個 getPassWordByUserNameServlet 請求的 Servlet
1 package com.ys.servlet;
2
3 import com.alibaba.fastjson.JSONObject;
4
5 import javax.servlet.ServletException;
6 import javax.servlet.annotation.WebServlet;
7 import javax.servlet.http.HttpServlet;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10 import java.io.IOException;
11
12 /**
13 * Create by YSOcean
14 */
15 @WebServlet("/getPassWordByUserNameServlet")
16 public class UserServlet extends HttpServlet{
17 @Override
18 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
19 String userName = req.getParameter("userName");
20 String passWord = null;
21 if(userName != null){
22 passWord = "123";
23 }
24 JSONObject jsonObject = new JSONObject();
25 jsonObject.put("passWord",passWord);
26 resp.getWriter().println(jsonObject.toJSONString());
27 }
28 }
③、在浏覽器中輸入 http://localhost:8081/JavaWeb02/index.jsp 連結,去調用該頁面的 ajax 函數
浏覽器給我們傳回了一個錯誤,這就是浏覽器同源政策導緻的跨域通路會報錯。那麼該如何解決呢?
3、跨域解決辦法
①、response 添加 header
我們在 Servlet 請求傳回時添加如下代碼:
1 //*表示支援所有網站通路,也可以額外配置相應網站
2 resp.setHeader("Access-Control-Allow-Origin", "*");
請求結果如下:
②、JSONP 方式
首先我們要修改 index.jsp 頁面的 ajax 請求:
1 $.ajax({
2 type:"get",
3 async:false,
4 url:"http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName=Tom",
5 dataType:"jsonp",//資料類型為jsonp
6 jsonp:"backFunction",//服務端用于接收callBack調用的function名的參數
7 success:function (data) {
8 alert(data["passWord"]);
9 },
10 error:function () {
11 alert("error");
12 }
13
14 });
注意:我們修改了 dataType 的資料類型為 jsonp,并且新增了 jsop 屬性值為 “backFunction”。
接着在 JavaWeb01 項目的 Servlet 中進行如下修改:
1 @WebServlet("/getPassWordByUserNameServlet")
2 public class UserServlet extends HttpServlet{
3 @Override
4 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
5 String userName = req.getParameter("userName");
6 String passWord = null;
7 if(userName != null){
8 passWord = "123";
9 }
10 JSONObject jsonObject = new JSONObject();
11 jsonObject.put("passWord",passWord);
12 //1、第一種方法:*表示支援所有網站通路,也可以額外配置相應網站
13 //resp.setHeader("Access-Control-Allow-Origin", "*");
14
15 //2、第二種方法:jsonp
16 String backFunction = req.getParameter("backFunction");
17 resp.getWriter().println(backFunction+"("+jsonObject.toJSONString()+")");
18
19 //resp.getWriter().println(jsonObject.toJSONString());
20 }
21 }
結果就不截圖了,下面講講這種方式的原理。
1、在同源政策下,在某個伺服器下的頁面是無法擷取到該伺服器以外的資料的,即一般的ajax是不能進行跨域請求的。但 img、iframe 、script等标簽是個例外,這些标簽可以通過src屬性請求到其他伺服器上的資料。利用 script标簽的開放政策,我們可以實作跨域請求資料,當然這需要伺服器端的配合。 Jquery中ajax 的核心是通過 XmlHttpRequest擷取非本頁内容,而jsonp的核心則是動态添加 <script>标簽來調用伺服器提供的 js腳本。
2、當我們正常地請求一個JSON資料的時候,服務端傳回的是一串 JSON類型的資料,而我們使用 JSONP模式來請求資料的時候服務端傳回的是一段可執行的 JavaScript代碼。因為jsonp 跨域的原理就是用的動态加載 script的src ,是以我們隻能把參數通過 url的方式傳遞,是以jsonp的 type類型隻能是get !
我們可以看上面的請求,浏覽器按 F12 顯示如下:
我們将這段路徑單獨複制出來:
http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName=Tom&backFunction=jQuery33107285685756141047_1532791502227&_=1532791502228
再看 Preview 頁:
也就是說對于上面的JSONP 請求,其實jQuery會轉化為:
1 <script type="text/javascript"
2 src="http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName=Tom&backFunction=jQuery33107285685756141047_1532791502227&_=1532791502228">
3 </script>
然後動态的去加載該 script 标簽的 src 屬性。
③、HttpClient 請求轉發
這種方式用戶端是向 JavaWeb02 項目發送請求,而不是上面的向 JavaWeb01 發送請求,然後在 JavaWeb02 的背景通過 HttpClient 将請求發送到 JavaWeb01,得到資料後傳回。這種方式相當于繞過浏覽器的同源機制,直接通過後端進行轉發。
index.jsp 的ajax請求如下:
1 $.ajax({
2 type:"get",
3 async:false,
4 url:"http://localhost:8081/JavaWeb02/ToGetPassWordServlet?userName=Tom",
5 dataType:"json",
6 success:function (data) {
7 alert(data['passWord']);
8 },
9 error:function () {
10 alert("error");
11 }
12
13 });
注意我們是在 JavaWeb02 項目下的index.jsp 發送請求,請求路徑也是 JavaWeb02 下的 Servlet。
package com.ys.servlet;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Create by YSOcean
*/
@WebServlet("/ToGetPassWordServlet")
public class ToGetPassWordServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//擷取使用者名
String userName = req.getParameter("userName");
CloseableHttpClient httpClient = HttpClients.createDefault();
//建立get請求
HttpGet hget = new HttpGet("http://localhost:8080/JavaWeb01/getPassWordByUserNameServlet?userName="+userName);
CloseableHttpResponse httpResponse = httpClient.execute(hget);
int code = httpResponse.getStatusLine().getStatusCode();
if(code == 200){
String result = EntityUtils.toString(httpResponse.getEntity());
resp.getWriter().print(result);
}
httpResponse.close();
httpClient.close();
}
}
④、nginx 轉發
原理很簡單: