當前位置:
首頁 > 知識 > Ajax跨域問題詳解

Ajax跨域問題詳解

1.什麼是Ajax跨域問題

客戶端Client通過Ajax方式向伺服器Server發送Ajax請求,想要得到響應數據,但是由於客戶端和伺服器不在同一個域(協議,域名或埠不一致),瀏覽器出於安全方面的考慮,會在Ajax請求的時候作校驗,校驗不通過時瀏覽器會在控制台會拋出一個類似於SEC7120: [CORS] 原點「http://localhost:8080」未在「http://localhost:8081/ajaxserver/hello」的 cross-origin資源的 Access-Control-Allow-Origin response header 中找到「http://localhost:8080」的跨域安全問題

2.為什麼會產生Ajax跨域問題

*瀏覽器限制:通俗一點講就是瀏覽器多管閑事,當發現客戶端和伺服器不在同一個域中時會對Ajax請求做校驗,校驗不通過就會產生跨域安全問題,並非伺服器不允許客戶端訪問(以下示例可以驗證)。

*跨域:當客戶端和伺服器的協議,域名,埠有一樣不一致時,瀏覽器就會認為是跨域。

*XMLHttpRequest請求(Ajax請求):如果發出的不是XMLHttpRequest請求,瀏覽器也不會報跨域問題(以下示例可以驗證)。

3.Ajax跨域問題示例(基於SpringBoot)

創建伺服器端:

[java] view plain copy

  1. import

    org.springframework.web.bind.annotation.RequestMapping;
  2. import

    org.springframework.web.bind.annotation.RestController;
  3. @RestController
  4. @RequestMapping("/ajaxserver")
  5. public

    class

    AjaxServerController {
  6. @RequestMapping("hello")
  7. public

    String getString(){
  8. System.out.println("************");
  9. return

    "Hello world!";

  10. }
  11. }

添加配置:

[html] view plain copy

  1. #配置埠號
  2. server.port=8081
  3. #熱部署生效
  4. spring.devtools.restart.enabled=true

驗證伺服器端(瀏覽器訪問http://localhost:8081/ajaxserver/hello):

Ajax跨域問題詳解

創建客戶端:

[java] view plain copy

  1. import

    org.springframework.stereotype.Controller;
  2. import

    org.springframework.web.bind.annotation.RequestMapping;
  3. @Controller
  4. @RequestMapping("/ajaxclient")
  5. public

    class

    AjaxClientController {

  6. @RequestMapping("/index")
  7. public

    String getIndex(){
  8. return

    "index";
  9. }
  10. }

創建靜態頁面index.html

[html] view plain copy

  1. <!DOCTYPE html

    >

  2. <html

    xmlns:th="http://www.thymeleaf.org"

    >

  3. <head>

  4. <meta

    charset="UTF-8"

    >

  5. <title>

    Ajax跨域請求

    </title>

  6. <!-- <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> -->
  7. <!-- 引入靜態資源文件-->
  8. <script

    th:src="@{/static/js/jquery-3.2.1.min.js}" type="text/javascript"

    ></script>

  9. </head>

  10. <body>

  11. <a

    href="#" onclick="getRequest();"

    >

    Ajax跨域請求

    </a>

  12. <script

    type="text/javascript"

    >

  13. function getRequest(){
  14. console.log("getRequest");
  15. $.ajax({
  16. url:"http://localhost:8081/ajaxserver/hello",
  17. dataType:"JSON",
  18. type:"POST",
  19. async:true,
  20. success:function(data){
  21. console.log(data);
  22. }
  23. });
  24. }
  25. </script>

  26. </body>

  27. </html>

添加配置:

[html] view plain copy

  1. ############################################################
  2. #
  3. # thymeleaf相關配置
  4. #
  5. ############################################################
  6. spring.thymeleaf.prefix=classpath:/templates/
  7. spring.thymeleaf.suffix=.html
  8. spring.thymeleaf.mode=HTML5
  9. spring.thymeleaf.encoding=UTF-8
  10. spring.thymeleaf.content-type=text/html
  11. #載入靜態資源文件
  12. spring.mvc.static-path-pattern=/static/**

引入依賴:

[html] view plain copy

  1. <!-- 引入thymeleaf依賴 -->
  2. <dependency>

  3. <groupId>

    org.springframework.boot

    </groupId>

  4. <artifactId>

    spring-boot-starter-thymeleaf

    </artifactId>

  5. </dependency>

啟動客戶端,訪問http://localhost:8080/ajaxclient/index

Ajax跨域問題詳解

點擊Ajax跨域問題鏈接,可以看到如下信息:

Ajax跨域問題詳解

瀏覽器控制台拋出如下錯誤信息:SEC7120: [CORS] 原點「http://localhost:8080」未在「http://localhost:8081/ajaxserver/hello」的 cross-origin 資源的 Access-Control-Allow-Origin response header 中找到「http://localhost:8080」。

此時,清空編輯器控制台信息,來驗證跨域問題並非伺服器不允許客戶端訪問。再一次請求伺服器,可以看到,編輯器控制台列印信息如下:

Ajax跨域問題詳解

此時,F12進入瀏覽器調試模式查看網路,伺服器沒有響應數據

Ajax跨域問題詳解

在index.html中添加如下鏈接來驗證如果發出的不是XMLHttpRequest請求,瀏覽器也不會報跨域問題

[html] view plain copy

  1. <a

    href="http://localhost:8081/ajaxserver/hello"

    >

    非Ajax請求

    </a>

訪問http://localhost:8080/ajaxclient/index可以看到如下信息:

Ajax跨域問題詳解

點擊非Ajax請求鏈接,可以看到如下信息:

Ajax跨域問題詳解

控制台沒有拋出Ajax跨域安全問題,並且伺服器返回了響應數據

4.Ajax跨域問題解決思路

Ajax跨域問題詳解

Ajax跨域問題產生的原因是瀏覽器限制,Ajax請求以及跨域,當三者同時滿足時才會產生Ajax跨域問題。基於這種情況,解決思路如下:

1>不讓瀏覽器做跨域校驗:可以通過一些參數設置禁止瀏覽器做限制,但是這需要客戶端都要做改動,因此不推薦。

2>發出不是XMLHttpRequest請求:JSONP可以動態創建一個script來發出跨域請求,但是瀏覽器不認為這是一個XMLHttpRequest請求。

3>支持跨域/隱藏跨域:當被調用方可以做一些修改時,被調用方可以設置參數來支持跨域(例如A域名調用B域名時,在返回的數據裡面加入一些欄位允許A域名調用);當被調用方不可以做一些修改時(例如需要請求www.baidu.com響應數據,而百度並非你的合作公司),此時需要調用方來做修改,通過一個代理,從瀏覽器發出的都是A域名的請求,在代理裡面把指定的URL轉到B域名里,此時瀏覽器認為就是同一個域名,就不會產生跨域問題。

5.Ajax跨域問題解決方法

基於Ajax跨域問題解決思路,整理了如下解決辦法:

1>不讓瀏覽器做跨域校驗

可以通過命令行的方式進行設置,具體操作參考:https://www.cnblogs.com/zhongxia/p/5416024.html

2>JSONP的方式

JSONP(JSON with Padding)是 JSON 的一種「使用模式」,可用於解決主流瀏覽器的跨域數據訪問的問題。通俗地說,JSONP是非官方協議,是一種約定,它約定了如果請求的參數里包含了指定的參數(默認是callback)就是一個JSONP請求,伺服器發現該請求是JSONP請求時就會把響應數據由原來的JSON對象改為JS代碼(JS代碼是函數調用的形式,函數名是callback參數的值,函數參數是原來要返回的JSON對象)。

將index.html中的Ajax請求的dataType改為JSONP,並修改伺服器代碼,添加如下類:

[java] view plain copy

  1. import

    org.springframework.web.bind.annotation.ControllerAdvice;
  2. import

    org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
  3. @SuppressWarnings("deprecation")
  4. @ControllerAdvice
  5. public

    class

    JSONPAdvice

    extends

    AbstractJsonpResponseBodyAdvice{
  6. public

    JSONPAdvice(){
  7. super

    ("callback");
  8. }
  9. }

然後重新訪問客戶端,可以看到:

Ajax跨域問題詳解

錯誤信息變為SCRIPT1004: SCRIPT1004: Expected ";"查看伺服器響應數據如下:

Ajax跨域問題詳解

伺服器已經正常響應,但是瀏覽器控制台依然報錯,這是為什麼呢?於是百度了這個錯誤,得到如下結果:http://www.codes51.com/itwd/2116389.html

Ajax跨域問題詳解

由此得知,介面不支持JSONP。JSONP弊端:(1)伺服器需要改動代碼支持(2)SJSONP只支持GET請求(可以驗證)將Ajax請求添加參數type:"POST",然後重新訪問客戶端可以看到如下信息(請求的方法依然是GET):

Ajax跨域問題詳解

(3)發送的不是XMLHttpRequest請求:這既是JSONP能解決跨域問題的原因,也是其弊端。因為XMLHttpRequest請求有很多新特性(例如非同步,各種事件),而在JSONP中無法使用。

3>支持跨域/隱藏跨域

(1)常見的J2EE架構圖

Ajax跨域問題詳解

客戶端Client向伺服器發送請求,先經過Apache/Nginx(http伺服器),當Apache/Nginx發現請求是靜態請求(js文件,css文件,圖片等)時,會直接將資源文件返回給客戶端Client而不會再轉發到Tomcat;當

[java] view plain copy

  1. import

    java.io.IOException;
  2. import

    javax.servlet.Filter;
  3. import

    javax.servlet.FilterChain;
  4. import

    javax.servlet.FilterConfig;
  5. import

    javax.servlet.ServletException;
  6. import

    javax.servlet.ServletRequest;
  7. import

    javax.servlet.ServletResponse;
  8. import

    javax.servlet.annotation.WebFilter;
  9. import

    javax.servlet.http.HttpServletResponse;
  10. @WebFilter(urlPatterns="/*")
  11. public

    class

    CrossFilter

    implements

    Filter {
  12. @Override
  13. public

    void

    init(FilterConfig filterConfig)

    throws

    ServletException {
  14. // TODO Auto-generated method stub
  15. }
  16. @Override
  17. public

    void

    doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  18. throws

    IOException, ServletException {
  19. System.out.println("CrossFilter");
  20. HttpServletResponse res=(HttpServletResponse) response;
  21. //res.addHeader("Access-Control-Allow-Origin", "*");
  22. res.addHeader("Access-Control-Allow-Origin", "http://localhost:8080");
  23. //res.addHeader("Access-Control-Allow-Methods", "*");
  24. res.addHeader("Access-Control-Allow-Methods", "POST");
  25. chain.doFilter(request, response);
  26. }
  27. @Override
  28. public

    void

    destroy() {
  29. // TODO Auto-generated method stub
  30. }
  31. }

此過濾器的作用是將響應頭添加兩個欄位Access-Control-Allow-Origin和Access-Control-Allow-Methods,前者表示支持跨域的域,後者表示支持跨域的方法。

修改控制層代碼(注意:控制層要返回JSON格式的數據,這是因為在Ajax請求時要求響應數據必須時JSON格式, 所以在返回數據時都需要轉為JSON格式)

JSONResult為LZ自己封裝的工具類,源碼地址https://github.com/Jasper2s/Study_Imooc/tree/master/SpringBoot/src/main/java/com/springboot/until

[java] view plain copy

  1. import

    org.springframework.web.bind.annotation.RequestMapping;
  2. import

    org.springframework.web.bind.annotation.RestController;
  3. import

    com.ajaxserver.utils.JSONResult;
  4. @RestController
  5. @RequestMapping("/ajaxserver")
  6. public

    class

    AjaxServerController {
  7. /**
  8. * 注意:由於在Ajax請求時要求響應數據必須時JSON格式
  9. * 所以在返回數據時都需要轉為JSON格式
  10. * @return
  11. */
  12. @RequestMapping("hello")
  13. public

    JSONResult getString(){
  14. System.out.println("AjaxServerController-->getString");
  15. return

    JSONResult.ok("Hello world!");
  16. }
  17. }<span stylex="background-color:transparent;color:rgb(79,79,79);float:none;font-size:16px;font-style:normal;font-variant:normal;font-weight:400;letter-spacing:normal;line-height:26px;text-align:justify;text-decoration:none;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;">
  18. </span>

然後,在項目的啟動程序AjaxServcerApplication上要添加@ServletComponentScan來掃描組件(否則,過濾器不會起作用)

最後,進入瀏覽器訪問http://localhost:8080/ajaxclient/index,查看控制台,可以看到:

Ajax跨域問題詳解

控制台的響應表頭多了兩個欄位Access-Control-Allow-Origin和Access-Control-Allow-Methods,此時我們查看數據是否列印出來了:

Ajax跨域問題詳解

由此說明,此次Ajax跨域訪問成功響應,並將數據成功返回給客戶端!

b.Apache配置

可參考https://www.imooc.com/video/16592

c.Nginx配置

可參考https://www.imooc.com/video/16591/0

d.Spring框架解決方案

可參考https://www.imooc.com/video/16593

(3)調用方解決跨域:Apache/Nginx(http伺服器)將客戶端Client所有的請求轉發到伺服器,此時瀏覽器發現所有的請求都是同一個域就不會產生跨域問題。a.Nginx配置可參考https://www.imooc.com/video/16594/0b.Apache配置可參考https://www.imooc.com/video/16595

6.請求分類

Ajax跨域問題詳解

(1)簡單請求:瀏覽器對於簡單請求往往先執行後判斷,例如:瀏覽器控制台報跨域安全問題時,請求依然有響應數據,這說明瀏覽器將該請求視為簡單請求,先執行然後再判斷是否存在跨域安全問題。

(2)非簡單請求:瀏覽器對於非簡單請求往往先判斷後執行,舉例說明:

發送JSON格式Ajax請求示例如下:

修改index.html(添加如下代碼)

[html] view plain copy

  1. <a

    href="#" onclick="getRequest2();"

    >

    非簡單請求

    </a>

  2. function getRequest2(){
  3. console.log("getRequest2");
  4. $.ajax({
  5. url:"http://localhost:8081/ajaxserver/getuser",
  6. dataType:"JSON",
  7. type:"POST",
  8. data:{"name":"Jasper","age":20,"sex":"man"},//請求的數據為JSON對象
  9. //cache:true,//表示請求可以被緩存
  10. async:true,
  11. success:function(data_response){
  12. console.log(data_response);
  13. },
  14. error:function(){
  15. alert("error");
  16. }
  17. });
  18. }

Controller層添加如下代碼:

[java] view plain copy

  1. @RequestMapping("getuser")
  2. public

    JSONResult getUser(UserVO userVO){
  3. System.out.println("AjaxServerController-->getUser");
  4. System.out.println(userVO.getName());
  5. return

    JSONResult.ok(userVO);
  6. }

添加UserVO類:

[java] view plain copy

  1. public

    class

    UserVO {
  2. private

    String name;
  3. private

    Integer age;
  4. private

    String sex;
  5. public

    String getName() {
  6. return

    name;
  7. }
  8. public

    void

    setName(String name) {
  9. this

    .name = name;
  10. }
  11. public

    Integer getAge() {
  12. return

    age;
  13. }
  14. public

    void

    setAge(Integer age) {
  15. this

    .age = age;
  16. }
  17. public

    String getSex() {
  18. return

    sex;
  19. }
  20. public

    void

    setSex(String sex) {
  21. this

    .sex = sex;
  22. }
  23. }

瀏覽器訪問http://localhost:8080/ajaxclient/index,點擊非簡單請求鏈接可以看到如下信息:

Ajax跨域問題詳解

可以看到瀏覽器發起了兩次getUser請求,第一次getUser請求是預檢命令,當預檢命令通過後,再發第二次請求然後伺服器進行響應。而簡單請求只有一次請求,如下圖:

Ajax跨域問題詳解

那麼問題又出現了,如果每次發送非簡單請求,瀏覽器都要發起兩次請求豈不很影響速度?如何只讓瀏覽器在第一次發起的非簡單請求中請求兩次,之後只請求一次呢(因為第一次預檢命令通過之後,就沒有必要在之後的每次請求都發送一次預檢命令)?可以通過設置緩存!Win10瀏覽器已經自動將預檢命令加入到緩存(從上圖可知預檢命令執行時間為0秒,且來自緩存)。當然,也可以通過res.addHeader("Access-Control-Max-Age", "3600");告訴瀏覽器在一個小時內可以緩存設置的頭部信息。

至此,Ajax跨域請求相關問題已全部介紹完畢,歡迎大家指出不足之處!

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

springmvc+jsp轉spring boot結構 前後端分離
一個製作Web圖案的組件css-doodle

TAG:程序員小新人學習 |