【路由中间件】(弃用)防止csrf攻击
现在可以采用cookie的SameSite来防范此类攻击
-
安装
npm i -S koa-csrf- 最新版本 3.0.8
npm i -S koa-session- 最新版本 6.2.0
- koa-csrf 的机制要依赖 session 能力,否则下面的ctx.csrf会是null
- 针对单服务器单进程可以用这个,但是还是建议更新为
koa-generic-session,由redis全局管理session
-
使用写法(全局添加,不推荐,会跟multer上传起冲突,除非自己加到action当中作为参数)
import session from "koa-session"; import CSRF from "koa-csrf";app.keys = [process.env.NEW_SESSION_KEY,process.env.OLD_SESSION_KEY]; app.use(session(app))app.use(new CSRF({ invalidTokenMessage: 'Invalid CSRF token', invalidTokenStatusCode: 403, excludedMethods: [ 'GET', 'HEAD', 'OPTIONS' ], disableQuery: false }));- 必须要提供密钥,这里要写到
.env中- 为什么要两个元素
- 第一个是新的key,第二个是旧的key
- 程序默认用第一个,然后这样可以无缝替换:
- 当你想要把第一个key废弃时
- 可以放到第二个旧key的位置
- 然后设置个新的在第一个新key的位置
- 这样不会让已签发的网页立刻失效
NEW_SESSION_KEY=xxxOLD_SESSION_KEY=yyy
- 为什么要两个元素
disableQuery- 设为false,表示允许通过URL字符串传递
_csrf - 但这样可能会通过 Referer 泄露
- 所以要尽量放到表单中,通过POST提交
- 设为false,表示允许通过URL字符串传递
- 必须要提供密钥,这里要写到
-
注意!!!
- 这个中间件,如果全局的话必须要在
bodyParser后面插入 - 但最好不要加到全局,而是作为单独中间件,加到需要渲染添加
_csrf值的页面路由中,然后用ctx.csrf引用import CSRF from "koa-csrf"; router.get('/hello', new CSRF({ invalidTokenMessage: 'Invalid CSRF token', invalidTokenStatusCode: 403, excludedMethods: [ 'GET', 'HEAD', 'OPTIONS' ], disableQuery: false }), async (ctx)=>{ let title = 'hello koa2' console.log(ctx.csrf) await ctx.render('client/hello',{ title, csrf: ctx.csrf }) })- 但session还是要加到全局
- 这个中间件,如果全局的话必须要在
-
然后在自己的ejs模板文件中
- 给form添加一个隐藏的input,里面指定csrf值
<form action="/api_v1/register" method="POST"> <input type="hidden" name="_csrf" value="<%= csrf %>" /> <input type="email" name="email" placeholder="Email" /> <input type="password" name="password" placeholder="Password" /> <button type="submit">Register</button> </form> - 渲染模板的时候,服务器要提供csrf字段,作为客户端提交的验证凭据
await ctx.render('index', { title: 'EJS !', csrf: ctx.csrf });
- 给form添加一个隐藏的input,里面指定csrf值
-
中间件会自动拦截所有
POST请求中的_csrf字段,自动完成csrf值的校验- 如果通过,就按正常的路由逻辑走
- 如果不通过,会返回前面指定的错误信息
Invalid CSRF token,以及403错误 - 所有的POST请求,都需要校验此字段,怎么办?
- 可以取消全局中间件,只放到要求安全性的路由里
- 也可以每次都
手动给body添加_csrf字段,这个字段由服务端渲染返回,赋值提交服务端- 但这样对于前后端分离的页面,怎么办???
-
依次检查如下字段
req.body._csrf req.query._csrf req.headers[‘csrf-token’] req.headers[‘xsrf-token’] req.headers[‘x-csrf-token’] req.headers[‘x-xsrf-token’] -
添加了session后,观察到cookie多了两项
koa.sess=eyJzZWNyZXQiOiJfcEdUN3ExdVlVX0I2SldQRlZ3SG5vcWwiLCJfZXhwaXJlIjoxNjM5NzM3MTg2NDE3LCJfbWF4QWdlIjo4NjQwMDAwMH0=; koa.sess.sig=r-lkJKc7sbj0E2XoKZlE4J6nopo- 用来区分是哪一个用户和会话,便于取出此会话内对应的secret做csrf校验
- 校验规则:
- 取
_csrf的前8位,与此secret合并,进行哈希,如果等于_csrf的后27位,则校验通过
- 取
- 同一用户可以签发好多个
_csrf,而名下只有一个secret- 所以就算打开新的页面,旧页面的
_csrf值也不会失效
- 所以就算打开新的页面,旧页面的
- 校验规则:
- 当页面刷新了之后,因为是存在网站的cookie中,所以这两个值都不会变
- 使得不受http协议无状态的影响
- 可以精准确认浏览器内好几次请求,都是同一个人发出的
- 用来区分是哪一个用户和会话,便于取出此会话内对应的secret做csrf校验