CSRF跨站請求僞造原理及解決方案 JsonWebToken使用方法

什麼是CSRF

CSRF(Cross-site request forgery)跨站請求僞造,跟XSS跨站腳本攻擊一樣,存在巨大的危害性。可以這樣來理解 :

攻擊者盜用了你的身份,以你的名義發送惡意請求,對服務器來講,這個請求是完全合法的,但是卻完成了攻擊者所期望的一個操作,而且你自己還不知道究竟是哪些操作。CSRF能夠做的事情包括:以你名義發送郵件,發消息,盜取你的賬號,甚至於購買商品,虛擬貨幣轉賬…造成的問題包括:個人隱私泄露以及財產安全。

CSRF攻擊原理如下:

  1. 用戶C打開瀏覽器,訪問受信任網站A,輸入用戶名和密碼請求登錄網站A;
  2. 在用戶信息通過驗證後,網站A產生Cookie信息並返回給瀏覽器,此時用戶登錄網站A成功,可以正常發送請求到網站A;
  3. 用戶未退出網站A之前,在同一瀏覽器中,打開一個TAB頁訪問網站B;
  4. 網站B返回一些攻擊性代碼,併發出一個請求要求訪問第三方站點A;
  5. 瀏覽器在接收到這些攻擊性代碼後,根據網站B的請求,在用戶不知情的情況下攜帶Cookie信息,向網站A發出請求。網站A並不知道該請求其實是由B發起的,所以會根據用戶C的Cookie信息以C的權限處理該請求,導致來自網站B的惡意代碼被執行。

來看一個例子:

比如你打開瀏覽器,正在訪問A網站,登錄了自己的賬號,進行查看、編輯一些正常操作。如下圖所示:
CSRF
此時,你打開了B網站,隨意點了一個鏈接(這個鏈接相當於操作了上面網站中的刪除按鈕)。
CSRF
接下來發生了不可思義的一幕,明明只是點了B網站頁面的一個鏈接,卻對A網站的數據進行了操作。
CSRF
出現這種現象是因爲,cookie在同一個瀏覽器窗口、不同TAB標籤頁,會被共享,也就是說,當你在同一個瀏覽器窗口(不管是不是同一個網站)向同一個服務器發送請求時,cookie都會被自動傳到服務器。黑客完全可以在不知道你的 cookie 的情況下利用瀏覽器自動發送的 cookie 通過服務器的安全驗證。

要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,並且該信息不能存在於 cookie 之中。

**解決思路:**在用戶登錄成功後,返回一個隨機token給瀏覽器,當每次用戶發送請求的時候,將token 主動發送給服務器端(爲了安全,不建議將token以參數的形式傳給服務器,可以將token存儲在請求頭中),服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認爲可能是 CSRF 攻擊而拒絕該請求。

接下來我們來看抵禦CSRF攻擊的方法:

JSON Web Token

JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案,可以用來解決跨站請求僞造(csrf)的漏洞。

下面這些都是服務端的操作。

1. 安裝

在終端中輸入以下命令安裝 jsonwebtoken
npm install jsonwebtoken
或者使用yarn安裝
yarn add jsonwebtoken

2. 引入

使用require將jsonwebtoken引入到模塊中

const jwt = require("jsonwebtoken");

3. 創建token

創建一個token,創建有兩種方式,一種是對稱加密,意思是使用同一個密鑰進行加密和解密;另一種是非對稱加密,意思是使用私鑰進行加密,使用公鑰進行解密。非對稱加密的安全係數比對稱加密要高。

  • jwt.sign(payload,key,option),創建token
    • 第一個參數payload 爲要加密的內容,可以是字符串、或者json格式。
    • 第二個參數key 爲密鑰,如果採用對稱加密的方式,則key是一個字符串;如果採用非對稱加密的方式,則key是PEM編碼的私鑰,非對稱加密時,要指定第三個參數option中 algorithm屬性的值(默認爲HS256)。

對稱加密:

const jwt = require("jsonwebtoken");
//生成token
let token = jwt.sign("admin111", "aaa");

非對稱加密:

在終端中輸入以下命令,生成私鑰和公鑰文件

openssl genrsa -out rsa_private_key.pem 1024
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

私鑰和公鑰文件文件如下:
在這裏插入圖片描述

const jwt = require("jsonwebtoken");
const fs = require("fs");
const path = require("path");
//讀取私鑰文件
let file=path.resolve(__dirname , "../key/rsa_private_key.pem");
let privateKey = fs.readFileSync(file);
//生成token
let token = jwt.sign("admin111", privateKey, { algorithm: 'RS256' });

4. 驗證token

生成的token是一個加密後的無序字符,當服務器收到token時,需要對它先進行解密操作再判斷是否正確。

  • jwt.verify(token,key,option),對token進行解密操作
    • 第一個參數token 爲瀏覽器請求發送過的token。
    • 第二個參數key 爲密鑰,如果採用對稱加密的方式,則key就是加密時使用的key;如果採用非對稱加密的方式,則key是PEM編碼的公鑰,非對稱加密時,要指定第三個參數option中 algorithm屬性的值(默認爲HS256)。

對稱加密方式:

const jwt = require("jsonwebtoken");
//解密token
let rs = jwt.verify(token, "aaa");
if(rs === "admin111"){
	//驗證通過
}

非對稱加密方式:

const jwt = require("jsonwebtoken");
const fs = require("fs");
const path = require("path");
//讀取公鑰文件
let file=path.resolve(__dirname , "../key/rsa_public_key.pem");
let publicKey = fs.readFileSync(file);
//解密token
let rs = jwt.verify(token, publicKey,{ algorithm: 'RS256' });
if(rs === "admin111"){
	//驗證通過
}

5. 服務端完整案例

上面講到,生成token後,將token存儲在響應頭中發送給瀏覽器。瀏覽器在每次發送請求的時候,將token存儲在請求頭中,再發送給服務端。下面是服務端token操作的完整案例,這裏使用對稱加密的方式:

var express = require('express');
const jwt = require("jsonwebtoken");
var app = express();

app.post("/login",(req, res) {
	let {username,password} = req.body;
	//創建token
	let token = jwt.sign(username, "aaa");
	//將token添加到響應頭中
    res.set("x-access-token", token);
	//返回數據給瀏覽器
	res.send({message: "登錄成功"});
});

app.get("/getInfo",(req,res){
	if (req.get("x-access-token")) {
		//獲取瀏覽器傳過來的token
		let token = req.get("x-access-token");
		//將token解密
		let rs = jwt.verify(token, "aaa");
		//判斷token是否正確
		if (rs === req.session.username) {
			//返回數據給瀏覽器
			res.send({data:"..."});
		}
	}
});

app.listen(3000)

用戶登錄成功,在響應頭中,我們就可以看到token的存在。
JsonWebToken

前端對token的操作

服務端創建token後,將token存儲到響應頭中返回給前端,前端需要對token進行存儲,每次請求的時候再將token進行發送。

這裏使用jquery來操作,存儲token:

$.ajax({
	url:"/api/user/login",
	data:{username:"admin",password:"123"},
	type:"post",
	success:(res,status,xhr)=>{
		//登錄成功後,獲取響應頭中的token
        let token=xhr.getResponseHeader("x-access-token");
        //將token存儲在localstorage中
        if(token) localStorage.setItem("token",token);
	},
	error:(err)=>{
		console.log(err);
	}
})

使用$.ajaxSetup(),在發送所有的請求前,將token寫入請求頭中:

$.ajaxSetup({
	//發送所有的請求前,將token寫入請求頭中
	beforeSend(xhr,setting){
		let token=localStorage.getItem("token");
		xhr.setRequestHeader("x-access-token",token);
	}
}

當瀏覽器發送請求時,在請求頭中,我們也可以看到token的存在。
JsonWebToken

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章