/ dotnet

WebApi 和 Vue 中 CSRF 的处理

什么是跨域

CSRF 全称 Cross-Site Request Forgery,意为跨站请求伪造,一般来说,服务器和客户端通过 Cookie 来标识和认证用户,用户通过浏览器访问服务器端地 Seesion ID 是不可能被第三方知道的,但是通过 CSRF 不需要知道 Session ID 就能发起攻击。

举个例子

假设 a 网站有一个留言程序,留言提交的接口如下:

http://a.com/message

用户通过POST提交content字段就能留言成功。服务器端会自动从 Session ID 数据中判断谁提交地数据,补足 username 和 updatetime 这两个字段后向数据库写入数据:

function(req,res){
    var content = req.body.content || '';
    var username = req.session.username;
    var feedback = {
        username = username,
        content = content,
        updatetime = Date.Now()
    };
    db.save(feedback,function(err)){
        res.writeHead(200);
        res.end('OK');
    }
}

正常的情况下,谁提交地留言,显示的就是谁的名字。如果攻击者发现这里存在的 CSRF 漏洞,那么他就可以在另一个网站( http://b.com/attack )上构建一个表单提交:

<form id="test" mothod="POST" action="http://a.com/message">
    <input type="hidden" name="content" value="test attack"/>
</form>
<script>
    $(function(){
        $('#test').submit();
    }
</script>

攻击者只要诱导某个 a 网站的用户访问 b 网站,就会自动在 a 网站提交一个留言,由于在提交到 a 网站的过程中,浏览器会自动把 a 网站的 cookie 发送到服务器,尽管请求是来自 b 网站的,但是服务器不知情,用户也不知情。

解决方式

解决 CSRF 攻击的方案有添加随机值的方式:

var generateRandom = function(len){
    return crypto.randomBytes(Math.ceil(len * 3 / 4)).toString('base64').slice(o,len);
}

为每个用户的 Session 赋一个随机值

var token = req.session._csrf || (req.session._csrf = generateRandom(24));

在做页面渲染的过程中,将这个_csrf的值告知前端:

<form id="test" mothod="POST" action="http://a.com/message">
    <input type="hidden" name="content" value="message content"/>
    <input type='hidden' name="_csrf" value="<%=_csrf%>">
</form>

由于该值是一个随机值,攻击者构建出相同随机值的难度相当大,我们只需要在接收端做一次校验就能判断该请求是否伪造:

function(req,res){
    var token = req.session._csrf || (req.session._csrf = generateRandom(24));
    var _csrf = req.body._csrf;
    if(token !== _csrf){
        res.writeHead(403);
        res.end("禁止访问")
    }else{
        handle(req,res);
    }
}

WebApi 中的跨域处理

浏览器会自动禁止跨域的访问,我们写程序的时候调用站外地址时就需要做特殊处理:
先通过nuget给项目添加名为Microsoft.AspNet.WebApi.Cors的包

Install-Package Microsoft.AspNet.WebApi.Cors

在 abp 项目中WebApiModuleInitialize方法中添加:

var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
GlobalConfiguration.Configuration.EnableCors(cors);

然后就实现跨域了……
其他类型的项目应该也一样。
你也可以在前端设置如下:

Vue 的跨域

vue 的 config 文件里可以设置proxyTable,可以通过映射请求地址来解决跨域的问题。

...
proxyTable: {
    '/api':{
        target:"http://localhost:61754",
        changeOrigin: true,
        pathRewrite:{'^/api':''}
      }
    },
...

如上设置,前端运行的端口就代理了端口61754的/api请求。

参考文档