File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ // 直接不用cookie,用 H5 的 localStorage ,但是兼容性可能较差、并且攻击者可以
2+ // XSS: alert(localStorage.getItem("access_token"));
3+ //
4+
5+ // login.js
6+ async function login ( username , password ) {
7+ const res = await fetch ( "https://api.example.com/login" , {
8+ method : "POST" ,
9+ headers : {
10+ "Content-Type" : "application/json"
11+ } ,
12+ body : JSON . stringify ( { username, password } )
13+ } ) ;
14+
15+ const data = await res . json ( ) ;
16+
17+ // token 存 localStorage
18+ localStorage . setItem ( "access_token" , data . token ) ;
19+
20+ console . log ( "登录成功,token 已保存" ) ;
21+ }
22+
23+
24+ // request.js
25+ function authFetch ( url , options = { } ) {
26+ const token = localStorage . getItem ( "access_token" ) ;
27+
28+ return fetch ( url , {
29+ ...options ,
30+ headers : {
31+ ...( options . headers || { } ) ,
32+ Authorization : `Bearer ${ token } `
33+ }
34+ } ) ;
35+ }
36+
37+ authFetch ( "https://api.example.com/profile" )
38+ . then ( res => res . json ( ) )
39+ . then ( data => console . log ( "用户信息:" , data ) ) ;
Original file line number Diff line number Diff line change 1+ // 防御力强
2+ // 但是可能误伤跨站资源、例如图片,所以也可以开启宽松 Lax 只扼杀 post ,放过 get
3+
4+ app . post ( "/login" , ( req , res ) => {
5+ const token = "session_id_abc123" ;
6+
7+ res . cookie ( "session_id" , token , {
8+ httpOnly : true , // JS 无法读取,防 XSS 偷 cookie
9+ secure : true , // 只能 HTTPS
10+ sameSite : "strict" , // 跨站请求不携带 cookie
11+ } ) ;
12+
13+ res . json ( { message : "登录成功" } ) ;
14+ } ) ;
Original file line number Diff line number Diff line change 1+ // 在用户操作时后端返回 CSRF cookie 这是一次性的,仅能用于当前操作
2+
3+ async function initCsrf ( ) {
4+ const res = await fetch ( "/csrf" , { credentials : "include" } ) ;
5+ const data = await res . json ( ) ;
6+ sessionStorage . setItem ( "csrfToken" , data . csrfToken ) ;
7+ }
8+
9+ async function transfer ( ) {
10+ const csrfToken = sessionStorage . getItem ( "csrfToken" ) ;
11+
12+ const res = await fetch ( "/transfer" , {
13+ method : "POST" ,
14+ credentials : "include" ,
15+ headers : {
16+ "Content-Type" : "application/json" ,
17+ "X-CSRF-Token" : csrfToken ,
18+ } ,
19+ body : JSON . stringify ( { to : "bob" , amount : 100 } ) ,
20+ } ) ;
21+
22+ console . log ( await res . json ( ) ) ;
23+ }
Original file line number Diff line number Diff line change 1+ // 查看请求头的 referer
2+ // 但是如果用了 base64 编码,请求头就不会携带 referer ,例如 network/cookie/CSRF/danger2Iframe.html
Original file line number Diff line number Diff line change 1+ # 长短期双Token
2+
3+ ** 短期** 令牌 Access Token 放在前端例如 ** localStorage** 中
4+
5+ ** 长期** 令牌 Refresh Token 放在 ** HttpOnly Cookie 中供服务器端** 使用(防XSS),且开启 Secure
6+
7+ 1 . 登录
8+ 1 . 前端发送 POST 请求
9+ 2 . 服务器验证成功后返回 长期令牌 - 浏览器自动放到 Cookie 中;
10+
11+ ``` jsx
12+ Set - Cookie: refresh_token= xxx; HttpOnly; Secure; SameSite= Lax; Max- Age= 7d
13+ ```
14+
15+ 并在 响应体 中返回 短期令牌 - 前端保存到内存(JS 变量)或者 localStorage 中
16+
17+ ` ` ` jsx
18+ {
19+ "access_token": "xxx",
20+ "expire_in": 900
21+ }
22+ ` ` `
23+
24+ 2. 调用业务 API 时,请求手动携带 Access Token
25+
26+ ` ` ` jsx
27+ GET /api/userinfo
28+ Authorization: Bearer ACCESS_TOKEN
29+ ` ` `
30+
31+ 1. 没有过期的话直接服务器验证然后返回
32+ 2. 如果服务器返回 ** 401 ** 表示短期令牌 Access Token 过期了,那么前端立即调用 POST / api/ refresh_token ** 携带上 长期令牌 refresh_token 去请求** ,服务器端验证长期令牌成功后会在响应体中返回新的 短期令牌 ,前端更新 Access Token
33+ 3. 如果返回 ** 403 ** 表示长期令牌也过期了,那么就需要重新登录
Original file line number Diff line number Diff line change 1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="UTF-8 ">
6+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
7+ < title > Document</ title >
8+ </ head >
9+
10+ < body >
11+ < img src ="https://bank.com/transfer?to=ceilf6&money=1000000 " alt ="">
12+ </ body >
13+
14+ </ html >
15+
16+ < style >
17+ img {
18+ display : none;
19+ }
20+ </ style >
Original file line number Diff line number Diff line change 1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="UTF-8 " />
6+ < meta http-equiv ="X-UA-Compatible " content ="IE=edge " />
7+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
8+ < title > Document</ title >
9+ </ head >
10+
11+ < body >
12+ < form action ="http://localhost:3000/transfer " method ="post ">
13+ < input type ="text " name ="to " value ="ceilf6 " />
14+ < input type ="text " name ="money " value ="1000000 " />
15+ </ form >
16+
17+ < script >
18+ document . querySelector ( 'form' ) . submit ( ) ;
19+ </ script >
20+ </ body >
21+
22+ </ html >
Original file line number Diff line number Diff line change 1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="UTF-8 " />
6+ < meta http-equiv ="X-UA-Compatible " content ="IE=edge " />
7+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
8+ < title > Document</ title >
9+ </ head >
10+
11+ < body >
12+ <!-- src="./danger2Attack.html" -->
13+ < iframe src ="data:text/html;base64,Li9kYW5nZXIyQXR0YWNrLmh0bWw= " frameborder ="0 " style ="display: none "> </ iframe >
14+ </ body >
15+
16+ </ html >
You can’t perform that action at this time.
0 commit comments