什么是csrf攻击?
csrf/xsrf又叫跨站请求伪造。
先说常见的登陆鉴权: 用户在你的网站登陆后,一般把登陆凭证(token)存储在cookie里,之后每次调接口都会自动携带,后端根据这条cookie鉴权,判定是登陆状态,进而允许进行安全操作。
虽然这种鉴权方式最为简便,但存在一种安全隐患,就是csrf。
csrf攻击者会利用http请求自动携带cookie的机制,在用户登陆后,引导用户点击它的攻击链接,进而拿到用户的token去进行恶意请求,比如购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
防御csrf攻击
思路:
由于csrf攻击者只能拿到cookie去干坏事,但它无法知道cookie里有什么,也拿不到其他有效信息。我们只需要除cookie外再加一道它做不到的验证就可以了。
前端首次加载页面的时候,调接口让后端植入一条csrfToken到cookie里。然后前端每次请求从cookie里取出然后放到请求头里给后端传输。
后端将植入给前端的csrfToken存储在session,然后一些安全接口(一般是除了get请求外的接口),请求时,需要先进行csrf比对,取出request请求头里的csrfToken和自己session里的csrfToken进行比对,完全一致才放行
代码实现
前端(react)
代码语言:javascript复制1//App.tsx
2//根组件,判断cookie里有没有csrfToken,没有就请求后端种植
3 useEffect(() => {
4 if (!cookie.get("csrf_token")) {
5 request("/all/getCsrfToken");
6 }
7 }, []);
代码语言:javascript复制1//request.ts
2//axios请求拦截器内置了csrf方法,如下配置即可自动取出cookie里的csrf并放到请求头
3request.interceptors.request.use((config: any) => {
4 // 开启顶部加载进度条
5 Nprogress.start();
6
7 config.xsrfHeaderName = "X-CSRF-TOKEN";
8 config.xsrfCookieName = "csrf_token";
9
10 return config;
11});
后端(koa2)
后端利用koa-csrf中间件实现
代码语言:javascript复制1yarn add koa-csrf
1、先封装一个csrf实例
代码语言:javascript复制1//先封装一个csrf实例
2const CSRF = require("koa-csrf");
3
4const csrfMD = new CSRF({
5 invalidSessionSecretMessage: "Invalid session secret",
6
7 invalidTokenMessage: "Invalid CSRF token",
8
9 invalidTokenStatusCode: 403,
10});
11
12module.exports = csrfMD;
参数:
invalidTokenMessage
使 koa 抛出的错误信息内容,默认值为:"Invalid CSRF token"。它可以是一个接收 ctx 作为参数的函数,函数最后返回错误信息内容。
invalidTokenStatusCode
验证失败时的响应状态码,默认值为:403(Forbidden)。跟 invalidTokenMessage 参数一样,它也会被传递给 ctx.throw,用于抛出错误和拒绝请求。
excludedMethods
排除的请求方法,默认值为:["GET", "HEAD", "OPTIONS"]。
disableQuery
是否禁止通过查询字符串传递 _csrf 校验 token,默认值为 false。如果校验 token 出现在 URL 中,则可能会通过 Referer 泄露,应尽量把 Token 放在表单中,把敏感操作由 GET 改为 POST。
2、写一个给前端种植csrfToken的接口(必须是get)
代码语言:javascript复制1const Csrf = require("@utils/csrf");
2
3router.get("/getCsrfToken", Csrf, async (ctx) => {
4 ctx.cookies.set("csrf_token", ctx.csrf, {
5 httpOnly: false,
6 maxAge: 24 * 60 * 60 * 1000,
7 });
8
9 ctx.body = "csrfToken种植成功!";
10});
3、需要防御csrf的接口(post|put|delete),使用csrf即可自动校验
代码语言:javascript复制1router.delete("/delete",Csrf, async (ctx) => {
2 const { id } = ctx.query;
3 const result = await sql(`delete from user where id="${id}"`);
4
5 ctx.body = result;
6});