CSRF(Cross-site request forgery)跨站请求伪造,跟XSS跨站脚本攻击一样,存在巨大的危害性。可以这样来理解 :
攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来讲,这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,而且你自己还不知道究竟是哪些操作。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
CSRF攻击原理如下:
来看一个例子:
比如你打开浏览器,正在访问A网站,登录了自己的账号,进行查看、编辑一些正常操作。如下图所示:
此时,你打开了B网站,随意点了一个链接(这个链接相当于操作了上面网站中的删除按钮)。
接下来发生了不可思义的一幕,明明只是点了B网站页面的一个链接,却对A网站的数据进行了操作。
出现这种现象是因为,cookie在同一个浏览器窗口、不同TAB标签页,会被共享,也就是说,当你在同一个浏览器窗口(不管是不是同一个网站)向同一个服务器发送请求时,cookie都会被自动传到服务器。黑客完全可以在不知道你的 cookie 的情况下利用浏览器自动发送的 cookie 通过服务器的安全验证。
要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不能存在于 cookie 之中。
**解决思路:**在用户登录成功后,返回一个随机token给浏览器,当每次用户发送请求的时候,将token 主动发送给服务器端(为了安全,不建议将token以参数的形式传给服务器,可以将token存储在请求头中),服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
接下来我们来看抵御CSRF攻击的方法:
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,可以用来解决跨站请求伪造(csrf)的漏洞。
下面这些都是服务端的操作。
在终端中输入以下命令安装 jsonwebtoken
npm install jsonwebtoken
或者使用yarn安装
yarn add jsonwebtoken
使用require将jsonwebtoken引入到模块中
const jwt = require("jsonwebtoken");
创建一个token,创建有两种方式,一种是对称加密,意思是使用同一个密钥进行加密和解密;另一种是非对称加密,意思是使用私钥进行加密,使用公钥进行解密。非对称加密的安全系数比对称加密要高。
对称加密:
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' });
生成的token是一个加密后的无序字符,当服务器收到token时,需要对它先进行解密操作再判断是否正确。
对称加密方式:
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"){
//验证通过
}
上面讲到,生成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后,将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);
}
}