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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值