淺談前端“源”相關
同源策略是前端安全的基石,想學好前端安全的第一步也是最重要的一步就是深入理解同源策略以及和同源策略相關的比如 cors csp jsonp 還有 xss 經常會涉及的 cookie 作用域,以及指令碼源的歸屬問題,原先只是簡單的瞭解,於是打算抽出一點時間再次強化一下對其的理解,以便後期能對前端的安全作出更加深入的分析和探究。
0X01 同源策略
為了下面的解釋讓人更好理解,我想要先解釋一下同源策略
1.什麼叫同源
同源,就是協議、域名、埠號相同的URL,但凡這裡面有一個不一樣,我們就認為是不同源
下面我們來舉一個實際的例子幫助我們理解一下
url | 是否同源 | 原因 |
---|---|---|
http://abc.com/other | 是 | 協議、埠、域名相同 |
https://abc.com | 否 | 協議不同(https) |
http://abc.com:81 | 否 | 埠不同(81) |
http://www.abc.com/ | 否 | 域名不同,頂級域名與www子域名不是一個概念 |
http://news.abc.com/ | 否 | 域名不同(news) |
注意:
這裡比較容易混淆的就是 abc.com 和 www.abc.com ,我們常常把 www作為主域名,但是實際上是不規範的,www其實也只是 abc.com 的一個子域名而已。
2.同源策略 Same-Origin-Policy(SOP)
瀏覽器採用同源策略, 在沒有明確授權的情況下 ,禁止頁面載入或執行與自身不同源的任何指令碼。
注意:這裡說的明確授權指的是下面會講到的 cors jsonp 等合理跨域方式
(1)同源策略又分為以下兩種:
1.DOM 同源策略:禁止對不同源頁面 DOM 進行操作。這裡主要場景是 iframe 跨域的情況,不同域名的 iframe 是限制互相訪問的(比如一個惡意網站的頁面通過iframe嵌入了銀行的登入頁面(二者不同源),如果沒有同源限制,惡意網頁上的javascript指令碼就可以在使用者登入銀行的時候獲取使用者名稱和密碼)
2.XMLHttpRequest 同源策略:禁止使用 XHR 物件向不同源的伺服器地址發起 HTTP 請求(這一點裡面其實包括了 ajax)。
(2)但是同源策略留了一些“後門”
1.頁面中的連結,重定向以及表單提交是不會受到同源策略限制的( 未授權情況下,ajax 的表單提交是不被允許的,但是普通的表單是可以直接跨域的 )。
2. <script>、<img>、<iframe>、<link>
這些包含 src 屬性的標籤可以載入跨域資源。但瀏覽器限制了JavaScript的許可權使其不能讀、寫載入的內容。
3.為什麼要有同源策略
很明顯,我們是為了解決瀏覽器瀏覽中的一些安全問題才出現的同源策略,舉一下兩個例子
(1)如果沒有 DOM 同源策略,也就是說不同域的 iframe 之間可以相互訪問,那麼黑客可以這樣進行攻擊:
1.做一個假網站,裡面用 iframe 巢狀一個銀行網站 http://mybank.com。
2.把 iframe 寬高啥的調整到頁面全部,這樣使用者進來除了域名,別的部分和銀行的網站沒有任何差別。
3.這時如果使用者輸入賬號密碼,我們的主網站可以跨域訪問到 http://mybank.com 的 dom 節點,就可以拿到使用者的賬戶密碼了。
(2)如果 XMLHttpRequest 同源策略,那麼黑客可以進行 CSRF(跨站請求偽造) 攻擊:
1.使用者登入了自己的銀行頁面 http://mybank.com,http://mybank.com 向用戶的 cookie 中新增使用者標識。
2.使用者瀏覽了惡意頁面 http://evil.com,執行了頁面中的惡意 AJAX 請求程式碼。
3. http://evil.com 向 http://mybank.com 發起 AJAX HTTP 請求, 瀏覽器會預設把 http://mybank.com 對應 cookie 也同時傳送過去 。
4.銀行頁面從傳送的 cookie 中提取使用者標識,驗證使用者無誤,response中返回請求資料。此時資料就洩露了,而且由於 Ajax 在後臺執行,使用者無法感知這一過程。
注意:
cookie 是瀏覽器根據你請求的頁面進行傳送的,而與從哪個頁面傳送過去請求無關。比如說,我訪問了 a.com 以後獲取了 a.com 的cookie ,然後我訪問 q.com ,由於 cookie 域的限制,瀏覽器在請求 q.com 的時候不會攜帶 a.com 的cookie ,但是當 q.com 有一個向 a.com 發起的請求的時候,瀏覽器就會自動的在這個請求中帶上 a.com 的 cookie。
4.關於同源的一些小結
(1)跨域讀:同源策略是不允許這種事情發生的,如果可以你就能使用 js 讀取嵌入在 iframe 中的頁面的 dom 元素,獲取敏感資訊了
(2)跨域寫:同源策略不阻止這種操作,比如向不同源的地址傳送 POST 請求等,但是這種允許只限制在普通表單(而且是在沒有 CSRF token 或者驗證 referer 的情況下),對於 ajax 這種方式也是預設不允許的,如果隨隨便便允許就會出現使用 ajax 來進行 CSRF 請求的情況了
(3)跨域嵌入:這種方式是預設允許的,我們可以在一個源中通過 iframe 嵌入 另一個源的頁面,但是如果想限制這種操作的話,我們可以設定 x-frame-options 這個頭,這樣設定了這個頭的頁面就允許被嵌入到不同源的頁面中了
0X02 跨域的方法
我們知道同源策略的出現是為了保證瀏覽器瀏覽的安全性,但是在實際的開發中,我們有些時候必須要對不同源的資源進行訪問(比如說使用 ajax 進行跨域的表單提交,再比如說前端開發人員需要呼叫後端開發人員提供的 api 介面獲取資料等),畢竟安全性也不能干擾正常的網站的開發工作,於是在基於同源策略的情況下一些跨域的方式出現了,最主要的方式就是 CORS 和 JSONP
1.CORS(跨域資源共享)
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓執行在一個 origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源。
實現 CORS 通訊的關鍵是伺服器。只要伺服器實現了 CORS 介面,就可以跨源通訊。
瀏覽器將CORS請求分成兩類: 簡單請求(simple request) 和 非簡單請求(not-so-simple request)
只要同時滿足以下兩大條件,就屬於簡單請求。
1.請求方法是以下三種方法之一:
HEAD GET POST
2.HTTP的頭資訊不超出以下幾種欄位:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值 application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求(最常見的就是 ajax 傳送 json 資料)。
瀏覽器對這兩種請求的處理方式是不一樣的
####(1)簡單請求
1.在請求中需要附加一個額外的 Origin 頭部,其中包含請求頁面的源資訊(協議、域名和埠),以便伺服器根據這個頭部資訊來決定給予何種響應。例如:Origin: http://www.K0rz3n.com
2.如果伺服器認為這個請求可以接受,就在 Access-Control-Allow-Origin頭部中回發相同的源資訊(如果是公共資源,可以回發 * )。例如:Access-Control-Allow-Origin: http://www.K0rz3n.com
3.沒有這個頭部或者有這個頭部但源資訊不匹配,瀏覽器就會駁回這個響應。正常情況下,瀏覽器會處理該響應。
例如 localhost:63343 通過Ajax請求 http://192.168.10.61:8080伺服器資源時就會出現如下異常:
注意:
請求和響應都不包含 cookie 資訊。如果需要包含 cookie 資訊,ajax 請求需要設定 xhr 的屬性withCredentials 為 true,伺服器需要設定響應頭部 Access-Control-Allow-Credentials:true。
(2)非簡單請求
瀏覽器在傳送真正的請求之前,會先發送一個 Preflight 請求給伺服器,這種請求使用 OPTIONS 方法,傳送下列頭部:
Origin:與簡單的請求相同。 Access-Control-Request-Method: 請求自身使用的方法。 Access-Control-Request-Headers: (可選)自定義的頭部資訊,多個頭部以逗號分隔。
例如:
OPTIONS /someData/ HTTP/1.1 Host: public-data.com ...... Origin: http://ambergarden.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-CUSTOM-HEADER
傳送這個請求後,伺服器可以決定是否允許這種型別的請求。伺服器通過在響應中傳送如下頭部與瀏覽器進行溝通:
Access-Control-Allow-Origin:與簡單的請求相同。 Access-Control-Allow-Methods: 允許的方法,多個方法以逗號分隔。 Access-Control-Allow-Headers: 允許的頭部,多個方法以逗號分隔。 Access-Control-Max-Age: 應該將這個 Preflight 請求快取多長時間(以秒錶示)。
例如:
HTTP/1.1 200 OK Access-Control-Allow-Origin: http://K0rz3n.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-CUSTOM_HEADER Access-Control-Max-Age: 1728000 ......
一旦伺服器通過 Preflight 請求允許該請求之後,以後每次瀏覽器正常的 CORS請求,就都跟簡單請求一樣了。
請求:
POST /someData/ HTTP/1.1 Host: public-data.com X-CUSTOM-HEADER: custom_header_value ...... [Payload Here]
響應:
HTTP/1.1 200 OK Access-Control-Allow-Origin: http://K0rz3n.com Content-Type: application/xml ...... [Payload Here]
(3)優缺點對比
優點
1.CORS 通訊與同源的 AJAX 通訊沒有差別,程式碼完全一樣,容易維護。
2.支援所有型別的 HTTP 請求。
##### 缺點
1.存在相容性問題,特別是 IE10 以下的瀏覽器。
2.第一次傳送非簡單請求時會多一次請求。
2.JSONP(JSON with Padding)
由於 script 標籤不受瀏覽器同源策略的影響,允許跨域引用資源。因此可以通過動態建立 script標籤,然後利用 src 屬性進行跨域,這也就是 JSONP 跨域的基本原理,jsonp 可以說是 json 的一種使用模式,其實 jsonp 我在之前的文章中也介紹過了傳送門。
(1)jsonp 的跨域流程
直接通過下面的例子來說明 JSONP 實現跨域的流程:
1.定義一個 回撥函式 handleResponse 用來接收返回的資料
function handleResponse(data) { console.log(data); };
2.動態建立一個 script 標籤,並且告訴後端回撥函式名叫 handleResponse
var body = document.getElementsByTagName('body')[0]; var script = document.gerElement('script'); script.src = 'http://K0rz3n.com/json?callback=handleResponse'; body.appendChild(script);
3.通過 script.src 請求 http://www.K0rz3n.com/json?callback=handleResponse
,
4.後端能夠識別這樣的 URL 格式並處理該請求,然後返回 handleResponse({"name": "K0rz3n"})
給瀏覽器
5.瀏覽器在接收到 handleResponse({"name": "K0rz3n"})
之後立即執行 ,也就是執行 handleResponse 方法,獲得後端返回的資料,這樣就完成一次跨域請求了。
(2)優缺點對比
優點
1.用於實現跟蹤使用者點選頁面或動態廣告曝光次數有較大的優勢。
缺點
1. 只支援 GET 請求(這個是比較大的一個限制條件) 。
2.只能瀏覽器與伺服器的單向通訊,因為瀏覽器不能訪問伺服器的響應文字。
3.img src 跨域
由於 img 標籤不受瀏覽器同源策略的影響,允許跨域引用資源。因此可以通過 img 標籤的 src 屬性進行跨域,這也就是 img src 跨域的基本原理。
(1)img src 的跨域流程
直接通過下面的例子來說明影象 Ping 實現跨域的流程:
var img = new Image(); //通過 onload 及 onerror 事件可以知道響應是什麼時候接收到的,但是不能獲取響應文字 img.onload = img.onerror = function() { console.log("Done!"); } //請求資料通過查詢字串形式傳送 img.src = 'http://www.K0rz3n.com/test?name=K0rz3n';
(2)優缺點對比
優點
1.用於實現跟蹤使用者點選頁面或動態廣告曝光次數有較大的優勢。
缺點
1.只支援 GET 請求。
2.只能瀏覽器與伺服器的單向通訊,因為瀏覽器不能訪問伺服器的響應文字。
4.Webserver Proxy 跨域
瀏覽器有跨域限制,但是伺服器不存在跨域問題,所以可以由伺服器請求所要域的資源再返回給客戶端。
伺服器代理是萬能的。
5.document.domain 跨域
(1)document.domain 的跨域流程
對於主域名相同,而子域名不同的情況,可以使用 document.domain 來跨域。這種方式非常適用於 iframe 跨域的情況。
比如,有一個頁面,它的地址是 http://www.K0rz3n.com,在這個頁面裡面有一個 iframe,它的 src 是 http://K0rz3n.com/b.html。很顯然,這個頁面與它裡面的 iframe 框架是不同域的,所以我們是無法通過在頁面中書寫 js 程式碼來獲取 iframe 中的東西的。
這個時候,document.domain 就可以派上用場了,我們只要把 http://www.K0rz3n.com/a.html 和 http://www.K0rz3n.com/b.html 這兩個頁面的 document.domain 都設成相同的域名就可以了。但要注意的是,document.domain 的設定是有限制的,我們只能把 document.domain 設定成 自身或更高一級的父域,且主域必須相同 。例如:a.b.K0rz3n.com 中某個文件的 document.domain 可以設成 a.b.K0rz3n.com、b.K0rz3n.com 、K0rz3n.com 中的任意一個,但是不可以設成 c.a.b.K0rz3n.com ,因為這是當前域的子域,也不可以設成 baidu.com,因為主域已經不相同了。
(2)document.domain 跨域的具體實現
例如,在頁面 http://www.K0rz3n.com/a.html 中設定document.domain:
<iframe src="http://www.K0rz3n.com/b.html" id="myIframe" onload="test()"> <script> document.domain = 'K0rz3n.com'; // 設定成主域 function test() { console.log(document.getElementById('myIframe').contentWindow); } </script>
在頁面 http://K0rz3n.com/b.html 中也設定 document.domain,而且這也是必須的,雖然這個文件的 domain 就是 K0rz3n.com,但是還是必須顯式地設定 document.domain 的值:
<script> document.domain = 'K0rz3n.com'; // document.domain 設定成與主頁面相同 </script>
這樣, http://www.K0rz3n.com/a.html 就可以通過 js 訪問到 http://K0rz3n.com/b.html 中的各種屬性和物件了。
6.window.name 跨域
(1) window.name 的跨域流程
window 物件有個 name 屬性,該屬性有個特徵:即在一個視窗(window)的生命週期內,視窗載入的所有的頁面(不管是相同域的頁面還是不同域的頁面)都是共享一個 window.name 的,每個頁面對 window.name 都有讀寫的許可權,window.name 是持久存在一個視窗載入過的所有頁面中的,並不會因新頁面的載入而進行重置。
(2) window.name 跨域的具體實現
通過下面的例子介紹如何通過 window.name 來跨域獲取資料的。
頁面 http://www.K0rz3n.com/a.html 的程式碼:
<iframe src="http://K0rz3n.com/b.html" id="myIframe" onload="test()" style="display: none;"> <script> function test() {//iframe載入 "http://K0rz3n.com/b.html 頁面後會執行該函式 var iframe = document.getElementById('myIframe'); //重置 iframe 的 onload 事件程式,此時經過後面程式碼重置 src 之後,http://www.K0rz3n.com/a.html //頁面與該 iframe 在同一個源了,可以相互訪問了 iframe.onload = function() { var data = iframe.contentWindow.name; // 獲取 iframe 裡的 window.name console.log(data); // hello world! }; //重置一個與 http://www.K0rz3n.com/a.html 頁面同源的頁面 iframe.src = 'http://www.K0rz3n.com/c.html'; } </script>
頁面 http://K0rz3n.com/b.html 的程式碼:
<script type="text/javascript"> // 給當前的 window.name 設定一個 http://www.K0rz3n.com/a.html 頁面想要得到的資料值 window.name = "hello world!"; </script>
7.location.hash 跨域
(1)location.hash 跨域的流程
location.hash 方式跨域,是子框架具有修改父框架 src 的 hash 值,通過這個屬性進行傳遞資料,且更改 hash 值,頁面不會重新整理。 但是傳遞的資料的位元組數是有限的。
(2)location.hash 跨域的具體實現
頁面 http://www.K0rz3n.com/a.html 的程式碼:
<iframe src="http://K0rz3n.com/b.html" id="myIframe" onload="test()" style="display: none;"> <script> // iframe載入 "http://K0rz3n.com/b.html 頁面後會執行該函式 function test() { //獲取通過 http://K0rz3n.com/b.html 頁面設定 hash 值 var data = window.location.hash; console.log(data); } </script>
頁面 http://K0rz3n.com/b.html 的程式碼:
<script type="text/javascript"> //設定父頁面的 hash 值 parent.location.hash = "world"; </script>
8.postMessage 跨域
(1)postMessage 跨域的流程
window.postMessage(message,targetOrigin) 方法是 HTML5 新引進的特性,可以使用它來向其它的 window 物件傳送訊息,無論這個 window 物件是屬於同源或不同源。這個應該就是以後解決 dom 跨域通用方法了。
呼叫 postMessage 方法的 window 物件是指要接收訊息的那一個 window 物件,該方法的第一個引數 message 為要傳送的訊息,型別只能為字串;第二個引數 targetOrigin 用來限定接收訊息的那個 window 物件所在的域,如果不想限定域,可以使用萬用字元 *。
需要接收訊息的 window 物件,可以通過監聽自身的 message 事件來獲取傳過來的訊息,訊息內容儲存在該事件物件的 data 屬性中。
(2)postMessage 跨域的具體實現
頁面 http://www.K0rz3n.com/a.html 的程式碼:
<iframe src="http://K0rz3n.com/b.html" id="myIframe" onload="test()" style="display: none;"> <script> //iframe載入 "http://K0rz3n.com/b.html 頁面後會執行該函式 function test() { // 獲取 http://K0rz3n.com/b.html 頁面的 window 物件, // 然後通過 postMessage 向 http://K0rz3n.com/b.html 頁面傳送訊息 var iframe = document.getElementById('myIframe'); var win = iframe.contentWindow; win.postMessage('我是來自 http://www.K0rz3n.com/a.html 頁面的訊息', '*'); } </script>
頁面 http://K0rz3n.com/b.html 的程式碼:
<script type="text/javascript"> // 註冊 message 事件用來接收訊息 window.onmessage = function(e) { e = e || event; // 獲取事件物件 console.log(e.data); // 通過 data 屬性得到傳送來的訊息 } </script>
0X03 cookie 的路徑和作用域
1.cookie 的路徑
Cookie 的路徑是在伺服器建立Cookie 時設定的,它的作用是決定瀏覽器訪問伺服器的某個資源時,
需要將瀏覽器端儲存的哪些Cookie 歸還給伺服器
如圖所示:
如上圖所示,瀏覽器端儲存的Cookie 有三個,分別是Cookie1、Cookie2 和Cookie3。
它們三個的訪問路徑分別為:
/Example/cookie /Example/ /Example1/cookie
瀏覽器訪問伺服器端的路徑為:
http://localhost:8080/Example/cookie/a/index.jsp
也就是說index.jsp 頁面的訪問路徑為:
/Example/cookie/a/
該路徑包含了Cookie1 和Cookie2 的路徑,因此在訪問index.jsp 時,瀏覽器會將Cookie1 和Cookie2
傳送給伺服器(簡單的說就是父路徑給子路徑)。這就是Cookie 的路徑的作用,其中涉及到訪問路徑。
如果伺服器建立Cookie 時沒有設定路徑,那麼該Cookie 的路徑是當前資源的訪問路徑。
例如:在index.jsp 頁面中建立了一個Cookie,index.jsp 頁面的訪問路徑為“/Example/“,那麼該Cookie 的路徑就是 /Example/。如果伺服器建立Cookie 時設定了路徑,那麼Cookie 的路徑就是設定的路徑,例如:
cookie.setPath(“/Example/cookie”),那麼該Cookie 的路徑就是/Example/cookie。
2.cookie 的作用域
Cookie 還有一個屬性就是域,Cookie 類中有設定和獲取cookie 域的方法,如下所示:
(1)Void setDomain(String pattern):設定cookie 的域;
(2)String getDomain():獲取cookie 的域,返回值的型別是String 型別。
一般我們很少使用cookie 的域,只有在多個二級域共享Cookie時才用。例如:www.baidu.com、zhidao.baidu.com、news.baidu.com、tieba.baidu.com 這些域可以理解是百度的子專案,現在要 在這些域中共享cookie ,就需要使用cookie 的域.
使用時需要注意以下兩點:
(1)設定domain 為:setDomain(“.baidu.com”);
(2)設定path 為:setPath(“/”)。
0X04指令碼源的歸屬問題
關於指令碼源的歸屬問題是這樣規定的,指令碼的源是由它所嵌入的頁面決定的,也就是說,如果 利用 xss 漏洞向某個頁面注入了 js 程式碼,那麼這段 js 程式碼就是屬於這個網站所屬的源的,就能對這個網站的內容進行操作,比如說讀取 dom 的內容等,這是非常危險的,因為瀏覽器沒有能力去鑑別這段程式碼是不是網站所有者自己寫進網頁的,於是就引出了 CSP 這個東西.
0X05 CSP
一個 編寫 CSP 的網站 https://www.cspisawesome.com/
1.什麼是CSP
全稱:Content Security Policy 內容安全策略,管理員可以定義多種策略的範圍, 這些策略可以被任意組合,不一定要實現全部
CSP定義了Content-Security-Policy HTTP頭來允許你建立一個 可信來源的白名單 ,使得瀏覽器只執行和渲染來自這些來源的資源,而不是盲目信任伺服器提供的所有內容。即使攻擊者可以找到漏洞來注入指令碼,但是因為來源不包含在白名單裡,因此將不會被執行。
2.CSP 包含哪些內容
(1)內容源
大多數的策略指令都需要一個或者多個內容源,這些內容源指明內容應該從什麼地方載入
(2)源列表
指定主機 主機可以使用 萬用字元,主機與主機之間使用空格分割
(3)關鍵字
關鍵字用來描述某些特別的內容源
1.’none’ :不匹配任何的URL,兩側的單引號必須
2.’self’ :與文件同源
3.’unsafe-inline’ :允許使用內聯資源,包括內聯的 <script>
內聯的 javascript:URL 內聯的事件處理函式 onerror 和內聯的 <style>
4.’unsafe-eval’:允許通過字串建立程式碼的方式
注意:unsafe-inline 和 unsafe-eval 都是不安全的,他會使網站有跨站指令碼的風險
(4)資料
data:允許使用 data:URL 是不安全的
mediatream:將 mediastream:URL 作為內容源
兩個例項:
Content-Security-Policy: default-src 'self' trustedscripts.foo.com Content-Security-Policy: default-src 'self'; img-src 'self' data:; media-src mediastream:
(5)指令
1.base-uri
base-uri 指令定義了 URI,它可以作為文件的基準 URL。如果沒有指定值,那麼任何 URI 都被允許。如果沒有指定這條指令,瀏覽器會使用 base 元素中的 URL。
base-uri source-list
2.child-src
child-src 指定定義了 web workers 以及巢狀的瀏覽上下文(如 <frame>
和 <iframe>
)的源。
child-src source-list
3.connect-src
connect-src 指令定義了請求、XMLHttpRequest、WebSocket 和 EventSource 的連線來源。
connect-src source-list
4.default-src
default-src 指令定義了那些沒有被更精確指令指定的(預設)安全策略。該指令包含了以下指令:
child-src connect-src font-src img-src media-src object-src script-src style-src default-src source-list
5.img-src
img-src指令指定影象和圖示的有效來源。
img-src source-list
6.script-src
script-src指令指定JavaScript的有效源。當包含script-src或default-src指令時,除非分別指定“unsafe-inline”和“unsafe-eval”,否則內聯指令碼和eval()將被禁用。
7.style-src
style-src指令指定樣式表的有效來源。這包括外部載入的樣式表和 <style>
元素和HTML樣式屬性的內聯使用。來自源列表中不包含的源的樣式表不會被請求或載入。當包含style-src或default-src指令時, <style>
元素和HTML樣式屬性的內聯使用將被禁用,除非您指定’unsafe-inline’。
8.object-src
object-src指令為 <object>
, <embed>
, <applet>
元素指定有效的源。
object-src source-list
0X06 總結
這篇文章主要是對一些前端或者瀏覽器中涉及到和 “源” 有關的一些東西的羅列和整理吧,很多東西來源於網路,夾雜了一些我個人的理解,全當做後期的備忘來用吧