成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Cors跨域(二):實現跨域Cookie共享的三要素

開發 前端
本文主角是大家耳熟能詳的Cookie,聊聊它在跨域情況下如何實現“共享”?大家都知道Cookie是需要遵守同源策略(SameSite)的,本文將以跨域Cookie信息共享為場景,進一步加深對Cors的了解。

[[405495]]

前言

你好,我是YourBatman。

上篇文章(Cors跨域(一):深入理解跨域請求概念及其根因)用超萬字的篇幅把Cors幾乎所有概念都掃盲了,接下來將逐步提出解決方案等實戰性問題以及查漏補缺。

本文主角是大家耳熟能詳的Cookie,聊聊它在跨域情況下如何實現“共享”?大家都知道Cookie是需要遵守同源策略(SameSite)的,本文將以跨域Cookie信息共享為場景,進一步加深對Cors的了解。

本文提綱

版本約定

  • JDK:8
  • Servlet:4.x
  • Tomcat:9.x

正文

Cookie是做web開發繞不過去的一個概念,即使隨著JWT技術的出現它早已褪色不少,但依舊有其發光發熱之地。譬如一些內網后臺管理系統、Portal門戶、SSO統一登錄等場景...

如若你是新時代的程序員朋友,可能從未使用過Cookie,但肯定聽過它的“傳說”。作為本文的主角,那么我們就來先認識下這位“老朋友”吧。

重識Cookie

Cookie中文名:曲奇餅干。

[[405496]]

當然,我們在與他人溝通時可不要使用中文名,還是使用Cookie本名吧~

什么是Cookie

一個看似簡單,實則不好回答的一個問題。

眾所周知,Http是無狀態協議(Tips:不要問我什么叫無狀態哈),每次請求都是對等的(從0開始的),服務器不知道用戶上一次做了什么,這嚴重阻礙了 交互式 Web應用程序的實現。有些場景服務端需要知道用戶的訪問狀態(如登錄狀態),這個時候怎么辦?

針對這種場景其實很容想到解決辦法:你來訪問我服務端的時候,我給你一個“東西”,然后下次你再訪問我(注意是訪問我才攜帶哦)的時候把它帶過來我就知道是你啦,簡單交互圖如下:

這里交互中所指的“東西”,在Web領域它就是Cookie。Cookie就是用來繞開HTTP的無狀態性的手段,它是Web的標準技術(是web標準而不局限于只是Servlet),隸屬于RFC6265,現今的所有的瀏覽器、服務器均實現了此規范。

用一個20年前就用的比喻再補充解釋下:你去銀行卡里存錢,第一次去銀行銀行會給你辦一張銀行卡(里面存放著你的姓名、身份證、余額等信息)。下次你再去銀行的時候,只需帶著這張銀行卡銀行就可以“識別”你,從而就可以存/取錢了。這里的銀行卡就類同于Http請求里的Cookie概念。

基于此銀行(卡)的比喻舉一反三,類比解釋同域Cookie、不同域Cookie、跨域Cookie共享的含義:

  • 同域Cookie:每次訪問的是同一個域下的不同頁面、API(每次去的是同一家銀行的不同網點,帶上這家銀行卡即可識別身份)
  • 不同域Cookie:同一個瀏覽器窗口內可能同時訪問A網站和B網站,它們均有各自的Cookie,但訪問A時只會帶上A的Cookie(你可能有不同銀行的多張銀行卡,而去某個銀行時只有帶著他們家的銀行卡才去有用嘛)
  • 跨域Cookie共享:訪問A站點時已經登錄從而保存姓名、頭像等基本信息,這時訪問該公司的B站點時就自然而然的能顯示出這些基本信息,也就是實現信息共享(在銀聯體系中A銀行辦理的卡也能在B銀行能取出錢來,也就是實現余額“共享”)

❝說明:Cookie實現跨域共享要求根域必須是一樣才行,比如都是www.baidu.com和map.baidu.com的根域都是 baidu.com。這道理就相當于只有加入了銀聯的銀行才能用銀行卡去任意一家銀聯成員行取錢一樣❞

Cookie的交互機制

下面這張圖完整的說明了Cookie的交互機制,共四個步驟:

  1. 瀏覽器(客戶端)發送一個請求到服務器
  2. 服務器響應。并在HttpResponse里增加一個響應頭:Set-Cookie
  3. 瀏覽器保存此cookie在本地,然后以后每次請求都帶著它,且請求頭為:Cookie
  4. 服務器收到請求便可讀取到此Cookie,做相應邏輯后給出響應

由此可見,Cookie用于保持請求狀態,而這個狀態依賴于瀏覽器端(客戶端)的本地存儲。

代碼示例

概念聊了有一會了,寫幾句代碼放松放松。下面演示一下這個交互過程:

服務端代碼:首次請求種植Cookie,以后(請求攜帶了)就只打印輸出Cookie內容

  1. /** 
  2.  * 在此處添加備注信息 
  3.  * 
  4.  * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/6/9 10:36 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Slf4j 
  10. @WebServlet(urlPatterns = "/cookie"
  11. public class CookieServlet extends HttpServlet { 
  12.  
  13.     @Override 
  14.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  15.         String requestURI = req.getRequestURI(); 
  16.         String method = req.getMethod(); 
  17.         String originHeader = req.getHeader("Origin"); 
  18.         log.info("收到請求:{},方法:{}, Origin頭:{}", requestURI, method, originHeader); 
  19.  
  20.         // 讀取Cookie 
  21.         List<Cookie> myCookies = new ArrayList<>(); 
  22.         if (req.getCookies() != null) { 
  23.             myCookies = Arrays.stream(req.getCookies()).filter(c -> c.getName().equals("name") || c.getName().equals("age")).collect(toList()); 
  24.         } 
  25.  
  26.         if (myCookies.isEmpty()) { // 種植Cookie 
  27.             Cookie cookie = new Cookie("name""YourBatman"); 
  28.             // cookie.setDomain("baidu.com"); 
  29.             cookie.setMaxAge(3600); 
  30.             resp.addCookie(cookie); 
  31.             cookie = new Cookie("age""18"); 
  32.             cookie.setMaxAge(3600); 
  33.             resp.addCookie(cookie); 
  34.         } else { 
  35.             myCookies.stream().forEach(c -> { 
  36.                 log.info("name:{} value:{} domain:{} path:{} maxAge:{} secure:{}", c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getMaxAge(), c.getVersion(), c.getSecure()); 
  37.             }); 
  38.         } 
  39.  
  40.         resp.getWriter().write("hello cookie..."); 
  41.     } 

瀏覽器訪問:http://localhost:8080/cookie,可以看到響應里帶有Cookie頭信息Set-Cookie告知瀏覽器要保存此Cookie,如下所示:

瀏覽器收到響應,并且依照Set-Cookie這個響應頭,在本地存儲上此Cookie(至于存在內存還是硬盤上,請參照文下的生命周期部分分解):

 

❝說明:除了name和age之外的cookie鍵值對不用關心,由于使用IDEA作為服務器交互的緣故才產生了它們❞再次發送本請求,它會將此域的Cookie全都都攜帶發給后端服務器,如下圖所示:

服務端打印輸出:可以看到服務端收到瀏覽器發送過來的Cookie了

  1. INFO  c.y.cors.java.servlet.CookieServlet - 收到請求:/cookie,方法:GET, Origin頭:null 
  2. INFO  c.y.cors.java.servlet.CookieServlet - name:name value:YourBatman domain:null path:null maxAge:-1 secure:0 
  3. INFO  c.y.cors.java.servlet.CookieServlet - name:age value:18 domain:null path:null maxAge:-1 secure:0 

這就是Cookie一次完整的交互過程。

這里有個細節需要特別注意:name和age的maxAge屬性值均為-1,表示這套cookie是會話級別的。也就是說你若換一個會話(如:重新打開瀏覽器的一個無痕窗口(不是標簽頁)),發送一個同樣的請求http://localhost:8080/cookie,請求頭里將看不到Cookie的任何蹤影,服務端會給其生成一套新Cookie。如下圖所示:

Cookie的生命周期

缺省情況下,Cookie的生命周期是Session級別(會話級別)。若想用Cookie進行狀態保存、資源共享,服務端一般都會給其設置一個過期時間maxAge,短則1小時、1天,長則1星期、1個月甚至永久,這就是Cookie的生命(周期)。

Cookie的存儲形式,根據其生命周期的不同而不同。這由maxAge屬性決定,共有這三種情況:

  1. maxAge > 0:cookie不僅內存里有,還會持久化到硬盤,也叫持久Cookie。這樣的話即使你關機重啟(甚至過幾天再訪問),這個cookie依舊存在,請求時依舊會攜帶
  2. maxAge < 0:一般值為-1,也就臨時Cookie。該Cookie只在內存中有(如session級別),一旦管理瀏覽器此Cookie將不復存在。值得注意的是:若使用無痕模式訪問也是不會攜帶此Cookie的喲
  3. maxAge = 0:內存中沒有,硬盤中也沒有了,也就立即刪除Cookie。此種case存在的唯一目的:服務瀏覽器可能的已存在的cookie,讓其立馬失效(消失)

❝Tips:請注意maxAge<0(負數)和maxAge=0的區別。前者會存在于內存,只有關閉瀏覽器or重啟才失效;后者是立即刪除❞當然啦,Cookie的生命周期除了受到后端設置的Age值來決定外,還有兩種方式可“改變”它:

JavaScript操作Cookie

  1. // 取cookie: 
  2. function getCookie(name) {            
  3.     var arr = document.cookie.split(';');            
  4.     for (var i = 0; i < arr.length; i++) { 
  5.         var arr2 = arr[i].split('='); 
  6.         var arrTest = arr2[0].trim(); // 此處的trim一定要加              
  7.         if (arrTest == name) { 
  8.             return arr2[1]; 
  9.         } 
  10.     } 
  11.  
  12. // 刪cookie: 
  13. function delCookie(name) { 
  14.     var exp = new Date(); 
  15.     exp.setTime(exp.getTime() - 1); 
  16.     var cval = getCookie(name); 
  17.     if (cval != null) { 
  18.         document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString(); 
  19.     } 

瀏覽器的開發者工具操作Cookie

Cookie的安全性和劣勢

Cookie存儲在客戶端,正所謂客戶端的所有東西都認為不是安全的,因此敏感的數據(比如密碼)盡量不要放在Cookie里。Cookie能提高訪問服務端的效率,但是安全性較差!

Cookie雖然有不少優點,但它也有如下明顯劣勢:

  • 每次請求都會攜帶Cookie,這無形中增加了流量開銷,這在移動端對流量敏感的場景下是不夠友好的
  • Http請求中Cookie均為明文傳輸,所以安全性成問題(除非用Https)
  • Cookie有大小限制,一般最大為4kb,對于復雜的需求來講就捉襟見肘

由于Cookie有不安全性和眾多劣勢,所以現在JWT大行其道。當然嘍,很多時候Cookie依舊是最好用的,比如內網的管理端、Portal門戶、UUAP統一登錄等。

Cookie的域和路徑

Cookie是不可以跨域的,隱私安全機制禁止網站非法獲取其他網站(域)的Cookie。概念上咱不用長篇大論,舉個例子你應該就懂了:

❝淘寶有兩個頁面:A頁面a.taotao.com/index.html和B頁面b.taotao.com/index.html,默認情況下A頁面和B頁面的Cookie是互相獨立不能共享的。若現在有需要共享(如單點登錄共享token ),我們只需要這么做:將A/B頁面創建的Cookie的path設置為“/”,domain設置為“.taobtao.com”,那么位于a.taotao.com和b.taotao.com域下的所有頁面都可以訪問到這個Cookie了。❞

  • domain:創建此cookie的服務器主機名(or域名),服務端設置。但是不能將其設置為服務器所屬域之外的域(若這都允許的話,你把Cookie的域都設置為baidu.com,那百度每次請求豈不要“累死”)

注:端口和域無關,也就是說Cookie的域是不包括端口的

  • path:域下的哪些目錄可以訪問此cookie,默認為/,表示所有目錄均可訪問此cookie

跨域Cookie共享

三個關鍵詞:跨域、Cookie、共享。Cookie是數據載體,跨域是場景,共享是需求。

代碼模擬跨域Cookie共享

前端頁面:發送跨域請求,為了方便模擬這里發送跨域的簡單請求即可(還不知道什么叫簡單請求?戳這里)

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>Cookie交互機制(跨域)</title> 
  6.     <!--導入Jquery--> 
  7.     <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> 
  8. </head> 
  9. <body> 
  10. <button id="btn">Cookie交互機制(跨域)</button> 
  11. <div id="content"></div> 
  12.  
  13. <script> 
  14.     $("#btn").click(function () { 
  15.         $.get("http://localhost:8080/corscookie"); 
  16.     }); 
  17. </script> 
  18. </body> 
  19. </html> 
 前端頁面托管在本地的63342端口上:http://localhost:63342/...

后端代碼:后端接口托管在8080端口上:http://localhost:8080/...

❝這就是最簡單的一個跨域場景,兩個域具有相同的domain,因此才有共享Cookie的可能。❞

  1. /** 
  2.  * 在此處添加備注信息 
  3.  * 
  4.  * @author YourBatman. <a href=mailto:yourbatman@aliyun.com>Send email to me</a> 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/6/9 10:36 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Slf4j 
  10. @WebServlet(urlPatterns = "/corscookie"
  11. public class CorsCookieServlet extends HttpServlet { 
  12.  
  13.     @Override 
  14.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
  15.         String requestURI = req.getRequestURI(); 
  16.         String method = req.getMethod(); 
  17.         String originHeader = req.getHeader("Origin"); 
  18.         log.info("收到請求:{},方法:{}, Origin頭:{}", requestURI, method, originHeader); 
  19.  
  20.         // 讀取Cookie 
  21.         List<Cookie> myCookies = new ArrayList<>(); 
  22.         if (req.getCookies() != null) { 
  23.             myCookies = Arrays.stream(req.getCookies()).filter(c -> c.getName().equals("name") || c.getName().equals("age")).collect(toList()); 
  24.         } 
  25.  
  26.         if (myCookies.isEmpty()) { // 種植Cookie 
  27.             Cookie cookie = new Cookie("name""YourBatman"); 
  28.             // cookie.setDomain("baidu.com"); 
  29.             cookie.setMaxAge(3600); 
  30.             resp.addCookie(cookie); 
  31.             cookie = new Cookie("age""18"); 
  32.             cookie.setMaxAge(3600); 
  33.             resp.addCookie(cookie); 
  34.         } else { 
  35.             myCookies.stream().forEach(c -> { 
  36.                 log.info("name:{} value:{} domain:{} path:{} maxAge:{} secure:{}", c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getMaxAge(), c.getVersion(), c.getSecure()); 
  37.             }); 
  38.         } 
  39.  
  40.         setCrosHeader(resp); 
  41.         resp.getWriter().write("hello cookie..."); 
  42.     } 
  43.  
  44.     private void setCrosHeader(HttpServletResponse resp) { 
  45.         resp.setHeader("Access-Control-Allow-Origin""http://localhost:63342"); 
  46.     } 

點擊按鈕,發送請求:

注意看,服務端代碼雖然resp.addCookie(cookie);添加了Cookie,但是Response響應里并沒有Set-Cookie這個頭哦。查看瀏覽器發現木有Cookie:

也許你會說,當然沒有啦,因為Response里沒有Set-Cookie頭嘛,但我們代碼里明明已經addCookie了呀。

這半截理論當然沒問題,現在我在服務端程序里補充一個響應頭:

  1. private void setCrosHeader(HttpServletResponse resp) { 
  2.     resp.setHeader("Access-Control-Allow-Origin""http://localhost:63342"); 
  3.     resp.setHeader("Access-Control-Allow-Credentials""true"); 

(重啟服務端應用),再次發送請求,響應如下:

可以看到響應中已經有Set-Cookie響應頭了,再次查看Cookie是否已被瀏覽器保存,同樣的比比臉還干凈:

瀏覽器沒有存儲Cookie。What?難道翻車了?No,下面教你如何解釋以及怎么破?

跨域Cookie共享的關鍵點

這里要討論的是跨域中Cookie的存儲問題:默認情況下,瀏覽器是不會去為你保存下跨域請求響應的Cookie的。具體現象是:跨域請求的Response響應了即使有Set-Cookie響應頭(且有值),瀏覽器收到后也是不會保存此cookie的。

要實現Cookie的跨域共享,有3個關鍵點:

  1. 服務端負責在響應中將Set-Cookie發出來(由Access-Control-Allow-Credentials響應頭決定)
  2. 瀏覽器端只要響應里有Set-Cookie頭,就將此Cookie存儲(由異步對象的withCredentials屬性決定)
  3. 瀏覽器端發現只要有Cookie,即使是跨域請求也將其帶著(由異步對象的withCredentials屬性決定)

為了滿足這三個關鍵點,在實施層面就有三要素來指導我們開發來解決此類問題。

跨域Cookie共享的三要素

首先確保服務端能正確的在響應中有Set-Cookie響應頭,這由Access-Control-Allow-Credentials: true來保證。因此服務端只需要做多加這一步即可:

  1. resp.setHeader("Access-Control-Allow-Credentials""true"); 

Access-Control-Allow-Credentials該頭是可選的,是個bool值,它若為true就有兩個作用:

在跨域請求的響應中允許Set-Cookie響應頭

瀏覽器收到響應后,瀏覽器根據此頭判斷是否讓自己的withCredentials屬性生效

所以就來到了第二個要素:XMLHttpRequest對象的withCredentials屬性。該屬性是一個Boolean類型,它指示了是否該使用類似cookies,authorization headers(頭部授權)或者TLS客戶端證書這一類資格證書來創建一個跨站點訪問控制(cross-site Access-Control)請求。

  1. var xhr = new XMLHttpRequest(); 
  2. ... 
  3. xhr.withCredentials = true

❝Jquery的Ajax寫法與此不同,但底層原理一樣❞官方的語言理解起來總是那么晦澀,翻譯成人話:當異步對象設置了withCredentials=true時,瀏覽器會保留下響應的Cookie等信息,并且下次發送請求時將其攜帶。因此要指示瀏覽器存儲Cookie并且每次跨域請求都攜帶,僅需加上此參數即可:

  1. $.ajax({ 
  2.     url: "http://localhost:8080/corscookie"
  3.     type: "GET"
  4.     xhrFields: { 
  5.         withCredentials: true 
  6.     }, 
  7.     crossDomain: true 
  8. }); 

以上兩個要素完成后,影響“結果”的還有最后一個要素。這個要素比較隱晦,也是很多同學/文章忽略的點。

服務端的Access-Control-Allow-Origin這個響應頭的值不能是通配符*,而只能是具體的值。否則出現報錯:

換句話講:瀏覽器端跨域請求對象一旦開啟withCredentials=true屬性,服務端跨域Origin將不能再用*通配符,否則CORS error!

三要素都滿足后(Access-Control-Allow-Credentials:true;Access-Control-Allow-Origin:http://localhost:63342;withCredentials=true),再次點擊發送請求,結果如下:

完美。

總結上篇文章對Cors進行了全面介紹,本文以跨域Cookie共享為場景,很好的對跨域知識點進行了補充,并且也補足了Cors里一個重要的響應頭Access-Control-Allow-Credentials的解釋,相信通過本文同學你能加深對Web中Cookie的了解,以及跨域情況下Cookie信息如何共享。

本文轉載自微信公眾號「BAT的烏托邦」,可以通過以下二維碼關注。轉載本文請聯系BAT的烏托邦公眾號。

 

責任編輯:姜華 來源: BAT的烏托邦
相關推薦

2014-08-19 10:36:02

AngularCORS

2019-04-10 10:32:16

CORSNginx反向代理

2013-11-27 10:23:23

2019-03-13 14:15:25

CORS跨域資源前端

2021-06-10 18:11:02

Cors跨域Web開發Cors

2022-04-29 09:11:14

CORS瀏覽器

2021-06-17 07:15:36

Cors跨域多域名

2011-04-18 14:23:37

javascriptHTML

2011-04-21 16:09:17

JavascriptCookie

2021-06-25 09:04:39

Cors跨域JSONP vs CO

2011-07-05 10:48:41

javascript

2023-12-20 14:42:59

2020-08-31 19:20:33

瀏覽器CORS跨域

2022-04-01 12:38:32

cookie代碼面試

2016-11-01 21:51:03

phpjavascript

2021-04-27 15:20:41

人工智能機器學習技術

2011-04-08 09:16:12

JavaScript

2024-05-20 09:28:44

Spring客戶端瀏覽器

2020-12-31 08:14:39

VueAxiosJavaScript

2024-01-31 07:55:52

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 狠狠操操| 成人av一区二区三区 | 色又黄又爽网站www久久 | 先锋资源在线 | 成人18亚洲xxoo | 三级在线免费观看 | 少妇精品亚洲一区二区成人 | 亚洲视频在线观看一区二区三区 | 婷婷色国产偷v国产偷v小说 | 国产精品欧美一区二区三区 | 久久曰视频 | 成人精品国产 | 亚洲一区二区视频在线播放 | 国产成人精品免费 | 手机在线不卡av | 日韩在线观看中文字幕 | 亚洲视频二区 | 国产婷婷色一区二区三区 | 91九色婷婷 | 日韩资源| 日本免费黄色一级片 | 91性高湖久久久久久久久_久久99 | 久久一区精品 | 欧美精品区 | 久久男人 | 精品九九九 | 一区二区三区四区电影 | 日韩精品一区二区三区 | 国产精品毛片一区二区三区 | 成年人免费网站 | 91久久精品国产 | 午夜丁香视频在线观看 | 69av网| 午夜视频在线免费观看 | 日韩aⅴ视频 | 狠狠爱视频 | 亚洲精品视频观看 | 一区二区在线 | 精品日本中文字幕 | 免费看黄视频网站 | 亚洲精品在线视频 |