IT牛人博客聚合网站 发现IT技术最优秀的内容, 寻找IT技术的价值 http://www.udpwork.com/ zh_CN http://www.udpwork.com/about hourly 1 Sat, 29 Apr 2017 01:44:50 +0800 <![CDATA[消费升级与内容投资的趋势——我的一次论坛分享]]> http://www.udpwork.com/item/16244.html http://www.udpwork.com/item/16244.html#reviews Fri, 28 Apr 2017 21:54:52 +0800 魏武挥 http://www.udpwork.com/item/16244.html 4月27日,我朝发夕返的跑了趟帝都,主要是去参加GMIC论坛的网易内容分论坛,应邀做了15分钟的分享。网上有一些速记版,由于未经本人审阅,错漏百出。这次分享的确有一些我在天奇阿米巴创投基金做投资合伙人的一些思考,故而与过往不同,我自己特别整理了一下,捋顺了文字,贴在这里与各位共同参详。

 

—— 正文的分割线 ——

 

我今天和大家分享一下消费升级和内容投资。这两件事情至少在我们天奇阿米巴看来是一件事情,今天中午吃饭时我的合伙人sandy还和我在讨论把我们的消费升级基金和文创基金合并做成一支基金。

先看一下这几个月都有一些什么样的内容项目获得投资。

(感谢新榜提供资料整理,主要聚焦于过千万级别的早中期投资案。)

当然获得投资的项目很多很多,表中罗列的只是过千万的早期投资案,且可能并不穷尽。我总结下来大概从2016年年底到今天,小半年的时间里面它的投资类型有哪些关键字?

第一个我们看到的是内容电商,利用内容形成流量的一个入口,然后再把这个流量转化成零售。在内容电商当中广义的内容电商包括导购。不过导购说白了还是一个广告模式,现在狭义的或者说我们认为真正是在做电商的一定是碰仓配的。比如年糕妈妈是比较正宗的电商,而一条则是以导购为主并不怎么碰仓配的。

第二个比较火的概念是知识付费,获投的知识付费项目更多是平台型的。假设有一个人开了一个收费专栏,这个收费专栏是不是可以获得投资至少我没有见到。这种端的生产在我的感觉中,这是一个很好的生意但它不需要投资,投资机构也从中获得利益有限。注意,能投资的项目和是不是好生意这两者并不能划等号。

第三个比较热的概念是富媒体,也就是视音频。今天中午听说一个流言,国内一个非常大的音频平台被百度看中,百度想拿150亿把它拿下,回头求证一下。快手这样的短视频平台和一些短视频或音频生产项目,都获得了比较好的估值投资。

依然还有内容分发渠道在获得投资,zaker这个项目和官方行政方面的考虑是有关系的。但总体来说,图文分发渠道获得早中期投融资的案例已经减少了,我们看到几个巨头把市场瓜分基本已经完成了。

投资项目中还有自媒体联盟自媒体平台,我称为内容创业的卖水机构。一些所谓联盟,其实就是派单公司,从甲方要单然后发给自媒体去做,这是广告或公关公司的套路。不过,它们获得投融资后,更多是希望借助资本进行一些内容项目的布局,是希望获得流量控制权而不仅仅是业务合作的融资。

还有一个概念是IP。去年有强IP拥有者把IP给卖了的案例,比如非常有名的案例是同道大叔。这是值得思考的,如果他觉得IP有非常大的前途为什么要卖,IP这件事情如果要规模化变现环节是很长的,我这里指的是大规模变现,不是做点广告倒腾点流量。

基于上述的投资特征,我有这样一些思考。

首先是:线上流量争夺战已基本结束。很难再看到成批量大号的出现,不否认可能会有一个号从来没听说过,忽然之间一夜之间成名。但是成批量的大号的出现基本上可能性已经很小了。

图文分发的渠道战也已经基本结束,基本是这么一个态势,今日头条也好、腾讯也好,包括今天阿里系的UC、几家门户以前做的新闻客户端,市场基本是这样了。

线上流量争夺战结束,它背后的原因是因为用户的增长红利已经结束了,我们移动互联网用户增长速度非常快,从08、09移动互联网起步到今天10年不到时间,移动互联网用户占有中国整体互联网用户的90%,基本饱和。10年时间已经蛮长了,诸位要知道94年中国互联网开始,花了11年的时间中国网民才刚刚过亿,就是2005年。相对于中位互联网发展,移动互联网发展是极快的。这是宏观数据。反过来,从微观角度来说,所有做号的人发现粉丝增长很困难,非常难。所以用户的增长红利已经结束了。

接下来是观念红利。我从一个科技自媒体caoz原来一位百度员工写的文章得到启发。他的文章提到“观念红利”,并认为这是这个时代最大的红利。不过他并没有提到到底是什么样的观念红利,观念在代际间到底有什么差别?需要继续琢磨60后的观念、70后观念、80后观念、90后观念到底有什么不一样。如果你能把握住这个观念红利,那就是你的消费升级也好、内容升级也好的业务升级。

我把这个观念红利大致罗列四个特点,这和前面一些获得投资的案例是有关系的:

第一,它是对品质的要求。

如果你制作的内容无论是图还是文章还是视频音频,希望不是粗制滥造。但是特别有意思的一点,用户不在乎你是抄的。一个案例叫差评,差评是非常有名的拼稿大王我称为洗稿能手,网上5、60个内容源每个地方拿一段拼一下就是一篇文章。

我知道现在有一些大号部署了100、200个甚至更多小号的矩阵,小号拿的都是大号文章重新拼稿,迅速把文稿堆起来,量产速度非常快。用户根本不在意,知乎上微博上还是其他地方拼来的,只要看得爽就可以。

在消费当中可以看到网易严选也是这样,网易严选明确告诉你这把刀不是双立人的,但是双立人生产的,相当于以前淘系a货洗白了。其实用户根本不在意山寨,质量可以就行,尤其是在一些和炫耀性消费无关的品类上。这是对过去品牌的重新洗牌。内容行业同样出现内容的重新洗牌。

第二对颜值的要求。要求制作非常精致。粗制滥造有可能火,蓝瘦香菇就是粗制滥造但很火的一个案例,但这其实很难重复。从商业角度发展当然要求内容是可以重复让它火的,今天做一个火的明天后天还能再做一个。从用户角度来说,颜值即正义。

网易的论坛上,得提一下网易的东西。阴阳师这个游戏里,玩家对颜值的要求就很有意思。我认识一些玩家,只培养颜值高的式神,管它厉害不厉害。甚至还有拼命就想要个颜值高的,费了老力搞来了,也不用。摆在仓库里看看也好啊,这是这部分玩家的心态。

阴阳师火起来的原因很多,但少不了一条:画风非常美。可见颜值的重要性。不过,阴阳师似乎忽略了一个问题,这个问题造成它现在并不像以前那么火了。

第三点,不要以为用户很勤奋。用户非常懒,像阴阳师需要用户拼命肝才能有点成就的,这事就不靠谱。更何况拼命肝大概率上可能还没什么所得。用户并不希望看到的内容或者需要消费的过程是非常繁复需要琢磨需要思考需要烧脑的,他不希望这样。所以制作内容的时候,一定考虑让用户非常简单明白你的意思。视音频这种流媒体,其实是很不需要动脑子的。也是今天火爆起来的原因之一。

我估计是O2O给惯出来的,现在做什么东西都是坐在椅子上可以解决的,除了上厕所以外。懒人经济嘛。

第四个特点是新奇特。依然对新鲜从来没有见过的东西充满好奇心。但新奇特带来另外一个效果就是用户忠诚度不高,其实我一直不太喜欢把公号订阅用户称为粉丝,我认为这个世界上大概对一些歌星偶像是有一些粉丝的,公号哪怕有100万订户、1000万订户真正粉丝不会太多,每一个公号都知道,订户量越大打开率越低。用户的注意力也是游移的。不要指望什么真正的大规模铁粉的存在。

今天在内容行业当中还是要突出这四个东西:品质、颜值(表现形式),照顾到用户懒惰的心理和好奇的心理,这四点是现在内容行业很明显的趋势。

接下来的时间是2017年年中夏天以后一直到2018年,我们会看到更多的B轮以上的投资。天使当然还会有,但会听到更多B轮以上的融资,B轮以上融资的特点是在于对已有的流量进行精细化的运作。

什么意思?比方像我们基金看内容项目时,我们会问创始人:你的变现模式是什么?我们是一支小基金,不像那些特别牛逼的基金不在乎创业项目的商业模式,有很多大的基金他喜欢说,只要有用户就可以了商业模式不重要,十年后肯定会有商业模式,我们等不了那么长时间,我们小基金嘛,挺急功近利的。

做内容的商业模式很容易想到广告和公关,这是媒体的两个基本商业价值,但广告公关流量运作是不精细的。为什么?

因为以前单向传播或大众传播时代的大众媒体,也是靠广告公关活着的,电视台从来没有精细地运作过他们的流量,报纸也没有精细地运作过他们的流量,这一层商业价值肯定是有的,但这种商业价值挖掘得不够。我们更看好的是所谓的内容电商,但是我们觉得内容电商不应该只是导购,如果只是导购依然是一种广告模式,依然不是一种精细化的运作。

从今天的90后的消费观念来看,即时消费是很明显的,这就是消费观念的变化。

即时消费是什么意思?你可能在场景当中完成了消费。过去我们的消费不是场景消费,你可以问一下你的父母或者年纪再大一点的长者,他是什么呢?他们是赶集。周末或假期去上海路的南京路,非常典型的赶集行为,时间中心化、地点中心化,跟100年前200年前赶集没有差别。

慢慢我们时间中心化被打破了,因为有淘宝可以7×24小时都去淘宝店,但地点还是中心化的,所以是半赶集状态,当然也夹杂每年一年一度的双十一或618这种大促节日,大促其实是赶集,时间、地点统一是中心化的。

但现在越来越多的消费其实在场景当中完成。有一种场景可能是这样的:在内容号里有一篇文章把一个东西吹得特别玄乎特别好,一激动就买了,这就是一种即时消费。

任何一个代际的人都有可能有即时消费,比如说旅游就是场景中的即时消费,无论年长年幼,都有旅游时买了一堆场景中觉得很值得买的购买经历。但年轻人的即时消费不仅仅是旅游,线下场景有,线上也有。

线上与内容构建的场景密切相关,所以,内容投资与观念迭代下的消费升级也密切相关。

这是我今天分享的内容。谢谢各位!

 

—— 首发 扯氮集 ——

版权说明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

消费升级与内容投资的趋势——我的一次论坛分享,首发于扯氮集

]]>
4月27日,我朝发夕返的跑了趟帝都,主要是去参加GMIC论坛的网易内容分论坛,应邀做了15分钟的分享。网上有一些速记版,由于未经本人审阅,错漏百出。这次分享的确有一些我在天奇阿米巴创投基金做投资合伙人的一些思考,故而与过往不同,我自己特别整理了一下,捋顺了文字,贴在这里与各位共同参详。

 

—— 正文的分割线 ——

 

我今天和大家分享一下消费升级和内容投资。这两件事情至少在我们天奇阿米巴看来是一件事情,今天中午吃饭时我的合伙人sandy还和我在讨论把我们的消费升级基金和文创基金合并做成一支基金。

先看一下这几个月都有一些什么样的内容项目获得投资。

(感谢新榜提供资料整理,主要聚焦于过千万级别的早中期投资案。)

当然获得投资的项目很多很多,表中罗列的只是过千万的早期投资案,且可能并不穷尽。我总结下来大概从2016年年底到今天,小半年的时间里面它的投资类型有哪些关键字?

第一个我们看到的是内容电商,利用内容形成流量的一个入口,然后再把这个流量转化成零售。在内容电商当中广义的内容电商包括导购。不过导购说白了还是一个广告模式,现在狭义的或者说我们认为真正是在做电商的一定是碰仓配的。比如年糕妈妈是比较正宗的电商,而一条则是以导购为主并不怎么碰仓配的。

第二个比较火的概念是知识付费,获投的知识付费项目更多是平台型的。假设有一个人开了一个收费专栏,这个收费专栏是不是可以获得投资至少我没有见到。这种端的生产在我的感觉中,这是一个很好的生意但它不需要投资,投资机构也从中获得利益有限。注意,能投资的项目和是不是好生意这两者并不能划等号。

第三个比较热的概念是富媒体,也就是视音频。今天中午听说一个流言,国内一个非常大的音频平台被百度看中,百度想拿150亿把它拿下,回头求证一下。快手这样的短视频平台和一些短视频或音频生产项目,都获得了比较好的估值投资。

依然还有内容分发渠道在获得投资,zaker这个项目和官方行政方面的考虑是有关系的。但总体来说,图文分发渠道获得早中期投融资的案例已经减少了,我们看到几个巨头把市场瓜分基本已经完成了。

投资项目中还有自媒体联盟自媒体平台,我称为内容创业的卖水机构。一些所谓联盟,其实就是派单公司,从甲方要单然后发给自媒体去做,这是广告或公关公司的套路。不过,它们获得投融资后,更多是希望借助资本进行一些内容项目的布局,是希望获得流量控制权而不仅仅是业务合作的融资。

还有一个概念是IP。去年有强IP拥有者把IP给卖了的案例,比如非常有名的案例是同道大叔。这是值得思考的,如果他觉得IP有非常大的前途为什么要卖,IP这件事情如果要规模化变现环节是很长的,我这里指的是大规模变现,不是做点广告倒腾点流量。

基于上述的投资特征,我有这样一些思考。

首先是:线上流量争夺战已基本结束。很难再看到成批量大号的出现,不否认可能会有一个号从来没听说过,忽然之间一夜之间成名。但是成批量的大号的出现基本上可能性已经很小了。

图文分发的渠道战也已经基本结束,基本是这么一个态势,今日头条也好、腾讯也好,包括今天阿里系的UC、几家门户以前做的新闻客户端,市场基本是这样了。

线上流量争夺战结束,它背后的原因是因为用户的增长红利已经结束了,我们移动互联网用户增长速度非常快,从08、09移动互联网起步到今天10年不到时间,移动互联网用户占有中国整体互联网用户的90%,基本饱和。10年时间已经蛮长了,诸位要知道94年中国互联网开始,花了11年的时间中国网民才刚刚过亿,就是2005年。相对于中位互联网发展,移动互联网发展是极快的。这是宏观数据。反过来,从微观角度来说,所有做号的人发现粉丝增长很困难,非常难。所以用户的增长红利已经结束了。

接下来是观念红利。我从一个科技自媒体caoz原来一位百度员工写的文章得到启发。他的文章提到“观念红利”,并认为这是这个时代最大的红利。不过他并没有提到到底是什么样的观念红利,观念在代际间到底有什么差别?需要继续琢磨60后的观念、70后观念、80后观念、90后观念到底有什么不一样。如果你能把握住这个观念红利,那就是你的消费升级也好、内容升级也好的业务升级。

我把这个观念红利大致罗列四个特点,这和前面一些获得投资的案例是有关系的:

第一,它是对品质的要求。

如果你制作的内容无论是图还是文章还是视频音频,希望不是粗制滥造。但是特别有意思的一点,用户不在乎你是抄的。一个案例叫差评,差评是非常有名的拼稿大王我称为洗稿能手,网上5、60个内容源每个地方拿一段拼一下就是一篇文章。

我知道现在有一些大号部署了100、200个甚至更多小号的矩阵,小号拿的都是大号文章重新拼稿,迅速把文稿堆起来,量产速度非常快。用户根本不在意,知乎上微博上还是其他地方拼来的,只要看得爽就可以。

在消费当中可以看到网易严选也是这样,网易严选明确告诉你这把刀不是双立人的,但是双立人生产的,相当于以前淘系a货洗白了。其实用户根本不在意山寨,质量可以就行,尤其是在一些和炫耀性消费无关的品类上。这是对过去品牌的重新洗牌。内容行业同样出现内容的重新洗牌。

第二对颜值的要求。要求制作非常精致。粗制滥造有可能火,蓝瘦香菇就是粗制滥造但很火的一个案例,但这其实很难重复。从商业角度发展当然要求内容是可以重复让它火的,今天做一个火的明天后天还能再做一个。从用户角度来说,颜值即正义。

网易的论坛上,得提一下网易的东西。阴阳师这个游戏里,玩家对颜值的要求就很有意思。我认识一些玩家,只培养颜值高的式神,管它厉害不厉害。甚至还有拼命就想要个颜值高的,费了老力搞来了,也不用。摆在仓库里看看也好啊,这是这部分玩家的心态。

阴阳师火起来的原因很多,但少不了一条:画风非常美。可见颜值的重要性。不过,阴阳师似乎忽略了一个问题,这个问题造成它现在并不像以前那么火了。

第三点,不要以为用户很勤奋。用户非常懒,像阴阳师需要用户拼命肝才能有点成就的,这事就不靠谱。更何况拼命肝大概率上可能还没什么所得。用户并不希望看到的内容或者需要消费的过程是非常繁复需要琢磨需要思考需要烧脑的,他不希望这样。所以制作内容的时候,一定考虑让用户非常简单明白你的意思。视音频这种流媒体,其实是很不需要动脑子的。也是今天火爆起来的原因之一。

我估计是O2O给惯出来的,现在做什么东西都是坐在椅子上可以解决的,除了上厕所以外。懒人经济嘛。

第四个特点是新奇特。依然对新鲜从来没有见过的东西充满好奇心。但新奇特带来另外一个效果就是用户忠诚度不高,其实我一直不太喜欢把公号订阅用户称为粉丝,我认为这个世界上大概对一些歌星偶像是有一些粉丝的,公号哪怕有100万订户、1000万订户真正粉丝不会太多,每一个公号都知道,订户量越大打开率越低。用户的注意力也是游移的。不要指望什么真正的大规模铁粉的存在。

今天在内容行业当中还是要突出这四个东西:品质、颜值(表现形式),照顾到用户懒惰的心理和好奇的心理,这四点是现在内容行业很明显的趋势。

接下来的时间是2017年年中夏天以后一直到2018年,我们会看到更多的B轮以上的投资。天使当然还会有,但会听到更多B轮以上的融资,B轮以上融资的特点是在于对已有的流量进行精细化的运作。

什么意思?比方像我们基金看内容项目时,我们会问创始人:你的变现模式是什么?我们是一支小基金,不像那些特别牛逼的基金不在乎创业项目的商业模式,有很多大的基金他喜欢说,只要有用户就可以了商业模式不重要,十年后肯定会有商业模式,我们等不了那么长时间,我们小基金嘛,挺急功近利的。

做内容的商业模式很容易想到广告和公关,这是媒体的两个基本商业价值,但广告公关流量运作是不精细的。为什么?

因为以前单向传播或大众传播时代的大众媒体,也是靠广告公关活着的,电视台从来没有精细地运作过他们的流量,报纸也没有精细地运作过他们的流量,这一层商业价值肯定是有的,但这种商业价值挖掘得不够。我们更看好的是所谓的内容电商,但是我们觉得内容电商不应该只是导购,如果只是导购依然是一种广告模式,依然不是一种精细化的运作。

从今天的90后的消费观念来看,即时消费是很明显的,这就是消费观念的变化。

即时消费是什么意思?你可能在场景当中完成了消费。过去我们的消费不是场景消费,你可以问一下你的父母或者年纪再大一点的长者,他是什么呢?他们是赶集。周末或假期去上海路的南京路,非常典型的赶集行为,时间中心化、地点中心化,跟100年前200年前赶集没有差别。

慢慢我们时间中心化被打破了,因为有淘宝可以7×24小时都去淘宝店,但地点还是中心化的,所以是半赶集状态,当然也夹杂每年一年一度的双十一或618这种大促节日,大促其实是赶集,时间、地点统一是中心化的。

但现在越来越多的消费其实在场景当中完成。有一种场景可能是这样的:在内容号里有一篇文章把一个东西吹得特别玄乎特别好,一激动就买了,这就是一种即时消费。

任何一个代际的人都有可能有即时消费,比如说旅游就是场景中的即时消费,无论年长年幼,都有旅游时买了一堆场景中觉得很值得买的购买经历。但年轻人的即时消费不仅仅是旅游,线下场景有,线上也有。

线上与内容构建的场景密切相关,所以,内容投资与观念迭代下的消费升级也密切相关。

这是我今天分享的内容。谢谢各位!

 

—— 首发 扯氮集 ——

版权说明 及 商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

消费升级与内容投资的趋势——我的一次论坛分享,首发于扯氮集

]]>
0
<![CDATA[博客数据迁移 & 广告贴]]> http://www.udpwork.com/item/16245.html http://www.udpwork.com/item/16245.html#reviews Fri, 28 Apr 2017 21:19:10 +0800 lox http://www.udpwork.com/item/16245.html 我发现这个站点虽然两年多不更新了,但还是有一些访问量,因此还是利用这些小流量给我最近在做的事情打些广告吧,哈~

另,我花了两周的时间,把 is-programmer 的原有的 126 篇文章全部迁移备份到新的 blog 平台了,这里是迁移过程: http://xiaohanyu.me/posts/2017-04-26-notes-about-migrating-data/

]]>
我发现这个站点虽然两年多不更新了,但还是有一些访问量,因此还是利用这些小流量给我最近在做的事情打些广告吧,哈~

另,我花了两周的时间,把 is-programmer 的原有的 126 篇文章全部迁移备份到新的 blog 平台了,这里是迁移过程: http://xiaohanyu.me/posts/2017-04-26-notes-about-migrating-data/

]]>
0
<![CDATA[OAuth2 RFC6749中文翻译]]> http://www.udpwork.com/item/16243.html http://www.udpwork.com/item/16243.html#reviews Fri, 28 Apr 2017 11:45:07 +0800 鸟窝 http://www.udpwork.com/item/16243.html 转自RFC 6749-OAuth 2.0授权框架简体中文翻译

1. 简介

在传统的客户端-服务器身份验证模式中,客户端请求服务器上限制访问的资源(受保护资源)时,需要使用资源所有者的凭据在服务器上进行身份验证。
资源所有者为了给第三方应用提供受限资源的访问,需要与第三方共享它的凭据。 这造成一些问题和局限:

  • 需要第三方应用存储资源所有者的凭据,以供将来使用,通常是明文密码。
  • 需要服务器支持密码身份认证,尽管密码认证天生就有安全缺陷。
  • 第三方应用获得的资源所有者的受保护资源的访问权限过于宽泛,从而导致资源所有者失去对资源使用时限或使用范围的控制。
  • 资源所有者不能仅撤销某个第三方的访问权限而不影响其它,并且,资源所有者只有通过改变第三方的密码,才能单独撤销这第三方的访问权限。
  • 与任何第三方应用的让步导致对终端用户的密码及该密码所保护的所有数据的让步。

OAuth通过引入授权层以及分离客户端角色和资源所有者角色来解决这些问题。
在OAuth中,客户端在请求受资源所有者控制并托管在资源服务器上的资源的访问权限时,将被颁发一组不同于资源所有者所拥有凭据的凭据。

客户端获得一个访问令牌(一个代表特定作用域、生命期以及其他访问属性的字符串),用以代替使用资源所有者的凭据来访问受保护资源。
访问令牌由授权服务器在资源所有者认可的情况下颁发给第三方客户端。客户端使用访问令牌访问托管在资源服务器的受保护资源。

例如,终端用户(资源所有者)可以许可一个打印服务(客户端)访问她存储在图片分享网站(资源服务器)上的受保护图片,而无需与打印服务分享自己的用户名和密码。
反之,她直接与图片分享网站信任的服务器(授权服务器)进行身份验证,该服务器颁发给打印服务具体委托凭据(访问令牌)。

本规范是为HTTP(RFC2616)协议量身定制。在任何非HTTP协议上使用OAuth不在本规范的范围之内。

OAuth 1.0协议(RFC5849)作为一个指导性文档发布,是一个小社区的工作成果。
本标准化规范在OAuth 1.0的部署经验之上构建,也包括其他使用案例以及从更广泛的IETF社区收集到的可扩展性需求。
OAuth 2.0协议不向后兼容OAuth 1.0。这两个版本可以在网络上共存,实现者可以选择同时支持他们。
然而,本规范的用意是新的实现支持按本文档指定的Auth 2.0,OAuth 1.0仅用于支持现有的部署。
OAuth 2.0协议与OAuth 1.0协议实现细节没有太多关联。熟悉OAuth 1.0的实现者应该学习本文档,而不对有关OAuth 2.0的结构和细节做任何假设。

Links

1.1. 角色

OAuth定义了四种角色:

  • 资源所有者

    能够许可受保护资源访问权限的实体。当资源所有者是个人时,它作为最终用户被提及。

  • 资源服务器

    托管受保护资源的服务器,能够接收和响应使用访问令牌对受保护资源的请求。

  • 客户端

    使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。术语“客户端”并非特指任何特定的的实现特点(例如:应用程序是否在服务器、台式机或其他设备上执行)。

  • 授权服务器

    在成功验证资源所有者且获得授权后颁发访问令牌给客户端的服务器。
    授权服务器和资源服务器之间的交互超出了本规范的范围。授权服务器可以和资源服务器是同一台服务器,也可以是分离的个体。一个授权服务器可以颁发被多个资源服务器接受的访问令牌。

Links

1.2. 协议流程

 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

图1:抽象的协议流程

图1中所示的抽象OAuth 2.0流程描述了四个角色之间的交互,包括以下步骤:

  • (A)客户端向从资源所有者请求授权。授权请求可以直接向资源所有者发起(如图所示),或者更可取的是通过作为中介的授权服务器间接发起。
  • (B)客户端收到授权许可,这是一个代表资源所有者的授权的凭据,使用本规范中定义的四种许可类型之一或 者使用扩展许可类型表示。授权许可类型取决于客户端请求授权所使用的方式以及授权服务器支持的类型。
  • (C)客户端与授权服务器进行身份认证并出示授权许可请求访问令牌。
  • (D)授权服务器验证客户端身份并验证授权许可,若有效则颁发访问令牌。
  • (E)客户端从资源服务器请求受保护资源并出示访问令牌进行身份验证。
  • (F)资源服务器验证访问令牌,若有效则满足该请求。

客户端用于从资源所有者获得授权许可(步骤(A)和(B)所示)的更好方法是使用授权服务器作为中介,如4.1节图3所示。

Links

1.3. 授权许可

授权许可是一个代表资源所有者授权(访问受保护资源)的凭据,客户端用它来获取访问令牌。本规范定义了四种许可类型——授权码、隐式许可、资源所有者密码凭据和客户端凭据——以及用于定义其他类型的可扩展性机制。

Links

1.3.1. 授权码

授权码通过使用授权服务器做为客户端与资源所有者的中介而获得。客户端不是直接从资源所有者请求授权,而是引导资源所有者至授权服务器(由在RFC2616中定义的用户代理),授权服务器之后引导资源所有者带着授权码回到客户端。

在引导资源所有者携带授权码返回客户端前,授权服务器会鉴定资源所有者身份并获得其授权。由于资源所有者只与授权服务器进行身份验证,所以资源所有者的凭据不需要与客户端分享。

授权码提供了一些重要的安全益处,例如验证客户端身份的能力,以及向客户端直接的访问令牌的传输而非通过资源所有者的用户代理来传送它而潜在暴露给他人(包括资源所有者)。

Links

1.3.2. 隐式许可

隐式许可是为用如JavaScript等脚本语言在浏览器中实现的客户端而优化的一种简化的授权码流程。在隐式许可流程中,不再给客户端颁发授权码,取而代之的是客户端直接被颁发一个访问令牌(作为资源所有者的授权)。这种许可类型是隐式的,因为没有中间凭据(如授权码)被颁发(之后用于获取访问令牌)。

当在隐式许可流程中颁发访问令牌时,发授权服务器不对客户端进行身份验证。在某些情况下,客户端身份可以通过用于向客户端传送访问令牌的重定向URI验证。访问令牌可能会暴露给资源所有者,或者对资源所有者的用户代理有访问权限的其他应用程序。

隐式许可提高了一些客户端(例如一个作为浏览器内应用实现的客户端)的响应速度和效率,因为它减少了获取访问令牌所需的往返数量。然而,这种便利应该和采用隐式许可的安全影响作权衡,如那些在10.310.16节中所述的,尤其是当授权码许可类型可用的时候。

Links

1.3.3. 资源所有者密码凭据

资源所有者密码凭据(即用户名和密码),可以直接作为获取访问令牌的授权许可。这种凭据只能应该当资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序),以及当其他授权许可类型(例如授权码)不可用时被使用。

尽管本授权类型需要对资源所有者凭据直接的客户端访问权限,但资源所有者凭据仅被用于一次请求并被交换为访问令牌。通过凭据和长期有效的访问令牌或刷新令牌的互换,这种许可类型可以消除客户端存储资源所有者凭据供将来使用的需要。

Links

1.3.4. 客户端凭据

当授权范围限于客户端控制下的受保护资源或事先与授权服务器商定的受保护资源时客户端凭据可以被用作为一种授权许可。典型的当客户端代表自己表演(客户端也是资源所有者)或者基于与授权服务器事先商定的授权请求对受保护资源的访问权限时,客户端凭据被用作为授权许可。

Links

1.4. 访问令牌

访问令牌是用于访问受保护资源的凭据。访问令牌是一个代表向客户端颁发的授权的字符串。该字符串通常对于客户端是不透明的。令牌代表了访问权限的由资源所有者许可并由资源服务器和授权服务器实施的具体范围和期限。

令牌可以表示一个用于检索授权信息的标识符或者可以以可验证的方式自包含授权信息(即令牌字符串由数据和签名组成)。额外的身份验证凭据——在本规范范围以外——可以被要求以便客户端使用令牌。

访问令牌提供了一个抽象层,用单一的资源服务器能理解的令牌代替不同的授权结构(例如,用户名和密码)。这种抽象使得颁发访问令牌比颁发用于获取令牌的授权许可更受限制,同时消除了资源服务器理解各种各样身份认证方法的需要。

基于资源服务器的安全要求访问令牌可以有不同的格式、结构及采用的方法(如,加密属性)。访问令牌的属性和用于访问受保护资源的方法超出了本规范的范围,它们在RFC6750等配套规范中定义。

Links

1.5. 刷新令牌

访问令牌是用于获取访问令牌的凭据。刷新令牌由授权服务器颁发给客户端,用于在当前访问令牌失效或过期时,获取一个新的访问令牌,或者获得相等或更窄范围的额外的访问令牌(访问令牌可能具有比资源所有者所授权的更短的生命周期和更少的权限)。颁发刷新令牌是可选的,由授权服务器决定。如果授权服务器颁发刷新令牌,在颁发访问令牌时它被包含在内(即图1中的步骤D)。

刷新令牌是一个代表由资源所有者给客户端许可的授权的字符串。该字符串通常对于客户端是不透明的。该令牌表示一个用于检索授权信息的标识符。不同于访问令牌,刷新令牌设计只与授权服务器使用,并不会发送到资源服务器。

+--------+                                           +---------------+
|        |--(A)------- Authorization Grant --------->|               |
|        |                                           |               |
|        |<-(B)----------- Access Token -------------|               |
|        |               & Refresh Token             |               |
|        |                                           |               |
|        |                            +----------+   |               |
|        |--(C)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(D)- Protected Resource --| Resource |   | Authorization |
| Client |                            |  Server  |   |     Server    |
|        |--(E)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(F)- Invalid Token Error -|          |   |               |
|        |                            +----------+   |               |
|        |                                           |               |
|        |--(G)----------- Refresh Token ----------->|               |
|        |                                           |               |
|        |<-(H)----------- Access Token -------------|               |
+--------+           & Optional Refresh Token        +---------------+

图2:刷新过期的访问令牌

图2中的所示流程包含以下步骤:

  • (A)客户端通过与授权服务器进行身份验证并出示授权许可请求访问令牌。
  • (B)授权服务器对客户端进行身份验证并验证授权许可,若有效则颁发访问令牌和刷新令牌。
  • (C)客户端通过出示访问令牌向资源服务器发起受保护资源的请求。
  • (D)资源服务器验证访问令牌,若有效则满足该要求。
  • (E)步骤(C)和(D)重复进行,直到访问令牌到期。如果客户端知道访问令牌已过期,跳到步骤(G),否 则它将继续发起另一个对受保护资源的请求。
  • (F)由于访问令牌是无效的,资源服务器返回无效令牌错误。
  • (G)客户端通过与授权服务器进行身份验证并出示刷新令牌,请求一个新的访问令牌。客户端身份验证要求基于客户端的类型和授权服务器的策略。
  • (H)授权服务器对客户端进行身份验证并验证刷新令牌,若有效则颁发一个新的访问令牌(和——可选地——一个新的刷新令牌)。

步骤(C)、(D)、(E)和(F)在本规范的范围以外,如第7节中所述。

Links

1.6. TLS版本

本规范任何时候使用传输层安全性(TLS),基于广泛的部署和已知的安全漏洞TLS的相应版本(或多个版本)将会随时间而变化。在本规范撰写时,TLS 1.2版RFC5246是最新的版本,但它具有非常局限的部署基础,可能还未准备好可以实现。TLS 1.0版RFC2246是部署最广泛的版本并将提供最宽泛的互操作性。

实现也可以支持满足其安全需求的其他传输层安全机制。

Links

1.7. HTTP重定向

本规范广泛采用了HTTP重定向,有此客户端或授权服务器引导资源所有者的用户代理到另一个目的地址。虽然本规范中的例子演示了HTTP 302状态码的使用,但是任何其他通过用户代理完成重定向的方法都是允许的并被考虑作为实现细节。

Links

1.8. 互操作性

OAuth 2.0提供了丰富的具有明确的安全性质的授权框架。然而,尽管在其自身看来是一个带有许多可选择组件的丰富且高度可扩展的框架,本规范有可能产生许多非可互操作的实现。

此外,本规范中留下一些必需组件部分或完全没有定义(例如,客户端注册、授权服务器性能、端点发现等)。没有这些组件,客户端必须针对特定的授权服务器和资源服务器被手动并专门配置,以进行互操作。

本框架设计具有一个明确的期望,即未来工作将定义实现完整的web范围的互操作性所需的规范性的配置和扩展。

Links

1.9. 符号约定

本规范中的关键词“必须”、“不能”、“必需的”、“要”、“不要”、“应该”、“不应该”、“推荐的”、“可以”以及“可选的”按RFC2119所述解释。
本规范使用RFC5234的扩展巴科斯-诺尔范式(ABNF)表示法。此外,来自“统一资源标识符(URI):通用语法”RFC3986的规则URI引用也包含在内。

某些安全相关的术语按照RFC4949中定义的意思理解。这些术语包括但不限于:“攻击”、“身份认证”、“授权”、“证书”、“机密”,“凭据”,“加密”,“身份”,“记号”,“签名”,“信任”,“验证”和“核实”。

除非另有说明,所有协议参数的名称和值是大小写敏感的。

Links

2.0 客户端注册

在开始协议前,客户端在授权服务器注册。客户端在授权服务器上注册所通过的方式超出了本规范,但典型的涉及到最终用户与HTML注册表单的交互。

客户端注册不要求客户端与授权服务器之间的直接交互。在授权服务器支持时,注册可以依靠其他方式来建立信任关系并获取客户端的属性(如重定向URI、客户端类型)。例如,注册可以使用自发行或第三方发行声明或通过授权服务器使用信任通道执行客户端发现完成。

当注册客户端时,客户端开发者应该:

  • 指定2.1节所述的客户端类型,
  • 提供它的如3.1.2节所述的客户端重定向URI,且
  • 包含授权服务器要求的任何其他信息(如,应用名称、网址、描述、Logo图片、接受法律条款等)。
  • 2.1.客户端类型
  • 2.2.客户端标识
  • 2.3.客户端身份验证
  • 2.3.1.客户端密码
  • 2.3.2.其他身份验证方法
  • 2.4.未注册的客户端

    2.1. 客户端类型

    根据客户端与授权服务器安全地进行身份验证的能力(即维护客户端凭据机密性的能力),OAuth定义了两种客户端类型:
  • 机密客户端
    能够维持其凭据机密性(如客户端执行在具有对客户端凭据有限访问权限的安全的服务器上),或者能够使用 其他方式保证客户端身份验证的安全性。
  • 公开客户端
    不能够维持其凭据的机密性(如客户端执行在由资源所有者使用的设备上,例如已安装的本地应用程序或基于Web浏览器的应用),且不能通过其他方式保证客户端身份验证的安全性。
    客户端类型的选择基于授权服务器的安全身份认证定义以及其对客户端凭据可接受的暴露程度。授权服务器不应该对客户端类型做假设。

客户端可以以分布式的组件集合实现,每一个组件具有不同的客户端类型和安全上下文(例如,一个同时具有机密的基于服务器的组件和公开的基于浏览器的组件的分布式客户端)。如果授权服务器不提供对这类客户端的支持,或不提供其注册方面的指导,客户端应该注册每个组件为一个单独的客户端。
本规范围绕下列客户端配置涉及:

  • Web应用程序
    Web应用是一个运行在Web服务器上的机密客户端。资源所有者通过其使用的设备上的用户代理里渲染的HTML用户界面访问客户端。客户端凭据以及向客户端颁发的任何访问令牌都存储在Web服务器上且不会暴露给资源所有者或者被资源所有者可访问。
  • 基于用户代理的应用
    基于用户代理的应用是一个公开客户端,客户端的代码从Web服务器下载,并在资源所有者使用的设备上的用户代理(如Web浏览器)中执行。协议数据和凭据对于资源所有者是可轻易访问的(且经常是可见的)。由于这些应用驻留在用户代理内,在请求授权时它们可以无缝地使用用户代理的功能。
  • 本机应用程序
    本机应用是一个安装、运行在资源所有者使用的设备上的公开客户端。协议数据和凭据对于资源所有者是可访问的。假定包含在应用程序中的任何客户端身份认证凭据可以被提取。另一方面,动态颁发的如访问令牌或者刷新令牌等凭据可以达到可接受的保护水平。至少,这些凭据被保护不被应用可能与之交互的恶意服务器接触。在一些平台上,这些凭证可能被保护免于被驻留在相同设备上的其他应用接触。

    2.2. 客户端标识

    授权服务器颁发给已注册客户端客户端标识——一个代表客户端提供的注册信息的唯一字符串。客户端标识不是一个秘密,它暴露给资源所有者并且不能单独用于客户端身份验证。客户端标识对于授权服务器是唯一的。

客户端的字符串大小本规范未定义。客户端应该避免对标识大小做假设。授权服务器应记录其发放的任何标识的大小。

2.3. 客户端身份验证

如果客户端类型是机密的,客户端和授权服务器建立适合于授权服务器的安全性要求的客户端身份验证方法。授权服务器可以接受符合其安全要求的任何形式的客户端身份验证。

机密客户端通常颁发(或建立)一组客户端凭据用于与授权服务器进行身份验证(例如,密码、公/私钥对)。授权服务器可以与公共客户端建立客户端身份验证方法。然而,授权服务器不能依靠公共客户端身份验证达到识别客户端的目的。

客户端在每次请求中不能使用一个以上的身份验证方法。

  • 2.3.1.客户端密码
  • 2.3.2.其他身份验证方法

    2.3.1. 客户端密码

    拥有客户端密码的客户端可以使用RFC2617中定义的HTTP基本身份验证方案与授权服务器进行身份验证。客户端标识使用按照附录B的“application/x-www-form-urlencoded”编码算法编码,编码后的值用作用户名;客户端密码使用相同的算法编码并用作密码。授权服务器必须支持HTTP基本身份验证方案,用于验证被颁发客户端密码的客户端的身份。例如(额外的换行仅用于显示目的):

    Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

此外,授权服务器可以使用下列参数支持在请求正文中包含客户端凭据:

  • client_id
    必需的。如2.2节所述的注册过程中颁发给客户端的客户端标识。
  • client_secret
    必需的。客户端秘密。 客户端可以忽略该参数若客户端秘密是一个空字符串。

使用这两个参数在请求正文中包含客户端凭据是不被建议的,应该限于不能直接采用HTTP基本身份验证方案(或其他基于密码的HTTP身份验证方案)的客户端。参数只能在请求正文中传送,不能包含在请求URI中。

例如,使用请求正文参数请求刷新访问令牌(第6节)(额外的换行仅用于显示目的):

 POST /token HTTP/1.1
 Host: server.example.com
 Content-Type: application/x-www-form-urlencoded
 grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

当使用密码身份验证发送请求时,授权服务器必须要求使用如1.6所述的TLS。

由于该客户端身份验证方法包含密码,授权服务器必须保护所有使用到密码的端点免受暴力攻击。

2.3.2. 其他身份验证方法

授权服务器可以支持任何与其安全要求匹配的合适的HTTP身份验证方案。当使用其他身份验证方法时,授权服务器必须定义客户端标识(注册记录)和认证方案之间的映射。

2.4. 未注册客户端

本规范不排除使用未注册的客户端。然而,使用这样的客户端超出了本规范的范围,并需要额外的安全性分析并审查其互操作性影响。

3. 协议端点

授权过程采用了两种授权服务器端点(HTTP资源):

  • 授权端点——客户端用其通过用户代理重定向从资源所有者获取授权。
  • 令牌端点——客户端用其将授权许可交换为访问令牌,通常伴有客户端身份验证。

以及一种客户端端点:

  • 重定向端点——授权服务器用其通过资源所有者用户代理向客户端返回含有授权凭据的响应。

并不是每种授权许可类型都采用两种端点。

扩展许可类型可以按需定义其他端点。

客户端通过何种方式获得授权端点的位置超出了本文档范围,但该位置通常在服务文档中提供。

端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

由于向授权端点的请求引起用户身份验证和明文凭据传输(在HTTP响应中),当向授权端点发送请求时,授权服务器必须要求如1.6节所述的TLS的使用。

授权服务器对于授权端点必须支持使用HTTP“GET”方法RFC2616,也可以支持使用“POST”的方法。

发送的没有值的参数必须被对待为好像它们在请求中省略了。授权服务器必须忽略不能识别的请求参数。 请求和响应参数不能包含超过一次。

3.1.1. 响应类型

授权端点被授权码许可类型和隐式许可类型流程使用。客户端使用下列参数通知授权服务器期望的许可类型:

  • response_type
    必需的。其值必须是如4.1.1节所述用于请求授权码的“code”,如4.2.1节所述用于请求访问令牌的“token”(隐式许可)或者如8.4节所述的一个注册的扩展值之中的一个。

扩展响应类型可以包含一个空格(%x20)分隔的值的列表,值的顺序并不重要(例如,响应类型“a b”与“b a”相同)。 这样的复合响应类型的含义由他们各自的规范定义。

如果授权请求缺少“response_type”参数,或者如果响应类型不被理解,授权服务器必须返回一个4.1.2.1所述的错误响应。

3.1.2. 重定向端点

在完成与资源所有者的交互后,授权服务器引导资源所有者的用户代理返回到客户端。授权服务器重定向用户代理至客户端的重定向端点,该端点是事先在客户端注册过程中或者当发起授权请求时与授权服务器建立的。

重定向端点URI必须是如RFC3986的3.4节所述的绝对URI。端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

3.1.2.1. 端点请求的机密性

当所请求的响应类型是“code”或“token”时,或者当重定向请求将引起在蜜柑凭据通过公开网络传输时,重定向端点应该要求使用1.6节所述的TLS。本规范没有强制使用TLS,因为在撰写本规范时,要求客户端部署TLS对于许多客户端开发者是一严重的困难。如果TLS不可用,授权服务器应该在重定向之前警告资源所有者有关非安全端点(例如,在授权请求期间现实一条信息)。

缺乏传输层安全可能对客户端及它被授权访问的受保护资源的安全具有严重影响。当授权过程用作一种客户端委托的对最终用户认证(例如,第三方登录服务)的形式时,使用传输层安全尤其关键。

3.1.2.2. 注册要求

授权服务器必须要求下列客户端注册它们的重定向端点:

  • 公开客户端。
  • 采用隐式许可类型的机密客户端。

授权服务器应该要求所有客户端在使用授权端点前注册它们的重定向端点。

授权服务器应该要求客户端提供完整的重定向URI(客户端可以使用“state”请求参数实现每请求自定义)。如果要求完整的重定向URI注册不可行,授权服务器应该要求注册URI方案、授权和路径(当请求授权时只允许客户端动态改变重定向URI的查询部分)。

授权服务器可以允许客户端注册多个重定向端点。

缺少重定向URI注册的要求,可能使攻击者如10.15所述将授权端点用作自由重定向端点。

3.1.2.3. 动态配置

如果多个重定向URI被注册,或者如果只有部分重定向URI被注册,或者如果没有重定向URI被注册,客户端都必须使用“redirect_uri”请求参数在授权请求中包含重定向URI。

当重定向URI被包含在授权请求中时,若有任何重定向URI被注册,授权服务器必须将接收到的值与至少一个已注册的重定向URI(或URI部分)按RFC3986第6节所述进行比较并匹配。如果客户端注册包含了完整的重定向URI,授权服务器必须使用RFC3986第6.2.1节中定义的简单字符串比较法比对这两个URI 。

3.1.2.4. 无效端点

如果由于缺失、无效或不匹配的重定向URI而验证失败,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

3.1.2.5. 端点内容

向客户端端点的重定向请求通常会引起由用户代理处理的HTML文档响应。如果HTML响应直接作为重定向请求的服务结果,任何包含在HTML文档中的脚本将执行,并具有对重定向URI和其包含的凭据的完全访问权限。

客户端不应该在重定向端点的响应中包含任何第三方的脚本(例如,第三方分析、社交插件、广告网络)。相反,它应该从URI中提取凭据并向另一个端点重定向用户代理而不暴露凭据(在URI中或其他地方)。如果包含第三方脚本,客户端必须确保它自己的脚本(用于从URI中提取凭据并从URI中删除)将首先执行。

3.2. 令牌端点

客户端通过提交它的授权许可或刷新令牌使用令牌端点获取访问令牌。令牌端点被用于除了隐式许可类型(因为访问令牌是直接颁发的)外的每种授权许可中。

客户端通过何种方式获得令牌端点的位置超出了本文档范围,但该位置通常在服务文档中提供。

端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

由于向令牌端点的请求引起明文凭据的传输(在HTTP请求和响应中),当向令牌端点发送请求时,授权服务器必须要求如1.6节所述的TLS的使用。

当发起访问令牌请求时,客户端必须使用HTTP“POST”方法。

发送的没有值的参数必须被对待为好像它们在请求中省略了。授权服务器必须忽略不能识别的请求参数。请求和响应参数不能包含超过一次。

3.2.1. 客户端身份验证

在向令牌端点发起请求时,机密客户端或其他被颁发客户端凭据的客户端必须如2.3节所述与授权服务器进行身份验证。客户端身份验证用于:

  • 实施刷新令牌和授权码到它们被颁发给的客户端的绑定。当授权码在不安全通道上向重定向端点传输时,或者 当重定向URI没有被完全注册时,客户端身份验证是关键的。
  • 通过禁用客户端或者改变其凭据从被入侵的客户端恢复,从而防止攻击者滥用被盗的刷新令牌。改变单套客户端凭据显然快于撤销一整套刷新令牌。
  • 实现身份验证管理的最佳实践,要求定期凭证轮转。轮转一整套刷新令牌可能是艰巨的,而轮转单组客户端凭据显然更容易。

在向令牌端点发送请求时,客户端可以使用“client_id”请求参数标识自己。向令牌端点的“authorization_code”和“grant_type”请求中,未经身份验证的客户端必须发送它的“client_id”,以防止自己无意中接受了本打算给具有另一个“client_id”的客户端的代码。这保护了客户端免于被替换认证码。(它没有对手保护起源提供额外的安全。)

3.3. 访问令牌范围

授权端点和令牌端点允许客户端使用“scope”请求参数指定访问请求的范围。反过来,授权服务器使用“scope”响应参数通知客户端颁发的访问令牌的范围。

范围参数的值表示为以空格分隔,大小写敏感的字符串。 由授权服务器定义该字符串。如果该值包含多个空格分隔的字符串,他们的顺序并不重要且每个字符串为请求的范围添加一个额外的访问区域。

scope = scope-token *( SP scope-token )
scope-token = 1*( %x21 / %x23-5B / %x5D-7E )

基于授权服务器的策略或资源拥有者的指示,授权服务器可以全部或部分地忽略客户端请求的范围。如果颁发的访问令牌范围和客户端请求的范围不同,授权服务器必须包含“scope”响应参数通知客户端实际许可的范围。

在请求授权时如果客户端忽略了范围参数,授权服务器必须要么使用预定义的默认值处理请求,要么使请求失败以指出无效范围。授权服务器应该记录它的范围需求和默认值(如果已定义)。

4. 获得授权

为了请求访问令牌,客户端从资源所有者获得授权。授权表现为授权许可的形式,客户端用它请求访问令牌。OAuth定义了四种许可类型:授权码、隐式许可、资源所有者密码凭据和客户端凭据。它还提供了扩展机制定义其他许可类型。

在图3中所示的流程包括以下步骤:

  • (A)客户端通过向授权端点引导资源所有者的用户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,一旦访问被许可(或拒绝)授权服务器将传送用户代理回到该URI。
  • (B)授权服务器验证资源拥有者的身份(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
  • (C)假设资源所有者许可访问,授权服务器使用之前(在请求时或客户端注册时)提供的重定向URI重定向用户代理回到客户端。重定向URI包括授权码和之前客户端提供的任何本地状态。
  • (D)客户端通过包含上一步中收到的授权码从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进行身份验证。客户端包含用于获得授权码的重定向URI来用于验证。
  • (E)授权服务器对客户端进行身份验证,验证授权代码,并确保接收的重定向URI与在步骤(C)中用于重定向客户端的URI相匹配。如果通过,授权服务器响应返回访问令牌与可选的刷新令牌。

  • 4.1.1.授权请求

  • 4.1.2.授权响应
  • 4.1.3.访问令牌请求
  • 4.1.4.访问令牌响应

    4.1.1. 授权请求

    客户端通过按附录B使用“application/x-www-form-urlencoded”格式向授权端点URI的查询部分添加下列参数构造请求URI:
  • response_type
    必需的。值必须被设置为“code”。
  • client_id
    必需的。如2.2节所述的客户端标识。
  • redirect_uri
    可选的。如3.1.2节所述。
  • scope
    可选的。如3.3节所述的访问请求的范围。
  • state
    推荐的。客户度用于维护请求和回调之间的状态的不透明的值。当重定向用户代理回到客户端时,授权服务器包含此值。该参数应该用于防止如10.12所述的跨站点请求伪造。

客户端使用HTTP重定向响应向构造的URI定向资源所有者,或者通过经由用户代理至该URI的其他可用方法。
例如,客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

授权服务器验证该请求,确保所有需要的参数已提交且有效。如果请求是有效的,授权服务器对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过经由其他方式确定批准)。

当确定决定后,授权服务器使用HTTP重定向响应向提供的客户端重定向URI定向用户代理,或者通过经由用户代理至该URI的其他可行方法。

4.1.2. 授权响应

如果资源所有者许可访问请求,授权服务器颁发授权码,通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的查询部分添加下列参数传递授权码至客户端:

  • code
    必需的。授权服务器生成的授权码。授权码必须在颁发后很快过期以减小泄露风险。推荐的最长的授权码生命周期是10分钟。客户端不能使用授权码超过一次。如果一个授权码被使用一次以上,授权服务器必须拒绝该请求并应该撤销(如可能)先前发出的基于该授权码的所有令牌。授权码与客户端标识和重定向URI绑定。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

客户端必须忽略无法识别的响应参数。本规范未定义授权码字符串大小。客户端应该避免假设代码值的长度。授权服务器应记录其发放的任何值的大小。

  • 4.1.2.1.错误响应

    4.1.2.1. 错误响应

    如果由于缺失、无效或不匹配的重定向URI而请求失败,或者如果客户端表示缺失或无效,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

如果资源所有者拒绝访问请求,或者如果请求因为其他非缺失或无效重定向URI原因而失败,授权服务器通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的查询部分添加下列参数通知客户端:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含无效的参数值、包含一个参数超过一次或其他不良格式。
    • unauthorized_client
      客户端未被授权使用此方法请求授权码。
    • access_denied
      资源所有者或授权服务器拒绝该请求。
    • unsupported_response_type
      授权服务器不支持使用此方法获得授权码。
    • invalid_scope
      请求的范围无效,未知的或格式不正确。
    • server_error
      授权服务器遇到意外情况导致其无法执行该请求。(此错误代码是必要的,因为500内部服务器错误HTTP状态代码不能由HTTP重定向返回给客户端)。
    • temporarily_unavailable
      授权服务器由于暂时超载或服务器维护目前无法处理请求。(此错误代码是必要的,因为503服务不可用HTTP状态代码不可以由HTTP重定向返回给客户端)。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。
    “error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。
  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。
    “error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

4.1.3. 访问令牌请求

客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:

  • grant_type
    必需的。值必须被设置为“authorization_code”。
  • code
    从授权服务器收到的授权码。
  • redirect_uri
    必需的,若“redirect_uri”参数如4.1.1节所述包含在授权请求中,且他们的值必须相同。
  • client_id
    必需的,如果客户端没有如3.2.1节所述与授权服务器进行身份认证。

如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如
必需的,如果客户端没有如3.2.1节所述与授权服务器进行身份验证。

例如,客户端使用TLS发起如下的HTTP请求(额外的换行符仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份,
  • 确保授权码颁发给了通过身份验证的机密客户端,或者如果客户端是公开的,确保代码颁发给了请求中的“client_id”,
  • 验证授权码是有效的,并
  • 确保给出了“redirect_uri”参数,若“redirect_uri”参数如4.1.1所述包含在初始授权请求中,且若包含,确保它们的值是相同的。

    4.1.4. 访问令牌响应

    如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

4.2. 隐式许可

隐式授权类型被用于获取访问令牌(它不支持发行刷新令牌),并对知道操作具体重定向URI的公共客户端进行优化。这些客户端通常在浏览器中使用诸如JavaScript的脚本语言实现。

由于这是一个基于重定向的流程,客户端必须能够与资源所有者的用户代理(通常是Web浏览器)进行交互并能够接收来自授权服务器的传入请求(通过重定向)。

不同于客户端分别请求授权和访问令牌的授权码许可类型,客户端收到访问令牌作为授权请求的结果。

隐式许可类型不包含客户端身份验证而依赖于资源所有者在场和重定向URI的注册。因为访问令牌被编码到重定向URI中,它可能会暴露给资源所有者和其他驻留在相同设备上的应用。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier     +---------------+
 |         -+----(A)-- & Redirection URI --->|               |
 |  User-   |                                | Authorization |
 |  Agent  -|----(B)-- User authenticates -->|     Server    |
 |          |                                |               |
 |          |<---(C)--- Redirection URI ----<|               |
 |          |          with Access Token     +---------------+
 |          |            in Fragment
 |          |                                +---------------+
 |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
 |          |          without Fragment      |     Client    |
 |          |                                |    Resource   |
 |     (F)  |<---(E)------- Script ---------<|               |
 |          |                                +---------------+
 +-|--------+
   |    |
  (A)  (G) Access Token
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |
 +---------+

注:说明步骤(A)和(B)的直线因为通过用户代理而被分为两部分。

图4:隐式许可流程

图4中的所示流程包含以下步骤:

  • (A)客户端通过向授权端点引导资源所有者的用户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,一旦访问被许可(或拒绝)授权服务器将传送用户代理回到该URI。
  • (B)授权服务器验证资源拥有者的身份(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
  • (C)假设资源所有者许可访问,授权服务器使用之前(在请求时或客户端注册时)提供的重定向URI重定向用户代理回到客户端。重定向URI在URI片段中包含访问令牌。
  • (D)用户代理顺着重定向指示向Web托管的客户端资源发起请求(按RFC2616该请求不包含片段)。用户代理在本地保留片段信息。
  • (E)Web托管的客户端资源返回一个网页(通常是带有嵌入式脚本的HTML文档),该网页能够访问包含用户代理保留的片段的完整重定向URI并提取包含在片段中的访问令牌(和其他参数)。
  • (F)用户代理在本地执行Web托管的客户端资源提供的提取访问令牌的脚本。
  • (G)用户代理传送访问令牌给客户端。

参见1.3.2节和第9节了解使用隐式许可的背景。

参见10.3节和10.16节了解当使用隐式许可时的重要安全注意事项。

4.2.1. 授权请求

客户端通过按附录B使用“application/x-www-form-urlencoded”格式向授权端点URI的查询部分添加下列参数构造请求URI:

  • response_type
    必需的。值必须设置为“token”。
  • client_id
    必需的。如2.2节所述的客户端标识。
  • redirect_uri
    可选的。如3.1.2节所述。
  • scope
    可选的。如3.3节所述的访问请求的范围。
  • state
    推荐的。客户度用于维护请求和回调之间的状态的不透明的值。当重定向用户代理回到客户端时,授权服务器包含此值。该参数应该用于防止如10.12所述的跨站点请求伪造。

客户端使用HTTP重定向响应向构造的URI定向资源所有者,或者通过经由用户代理至该URI的其他可用方法。

例如,客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

授权服务器验证该请求,确保所有需要的参数已提交且有效。授权服务器必须验证它将重定向访问令牌的重定向URI与如3.1.2节所述的客户端注册的重定向URI匹配。

如果请求是有效的,授权服务器对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过经由其他方式确定批准)。

当确定决定后,授权服务器使用HTTP重定向响应向提供的客户端重定向URI定向用户代理,或者通过经由用户代理至该URI的其他可行方法。

4.2.2. 访问令牌响应

如果资源所有者许可访问请求,授权服务器颁发访问令牌,通过使用按附录B的“application/x-www-form-urlencoded”格式向重定向URI的片段部分添加下列参数传递访问令牌至客户端:

  • access_token
    必需的。授权服务器颁发的访问令牌。
  • token_type
    必需的。如7.1节所述的颁发的令牌的类型。值是大小写敏感的。
  • expires_in
    推荐的。以秒为单位的访问令牌生命周期。例如,值“3600”表示访问令牌将在从生成响应时的1小时后到期。如果省略,则授权服务器应该通过其他方式提供过期时间,或者记录默认值。
  • scope
    可选的,若与客户端请求的范围相同;否则,是必需的。如3.3节所述的访问令牌的范围。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。授权服务器不能颁发刷新令牌。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:(额外的换行符仅用于显示目的):

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600

开发人员应注意,一些用户代理不支持在HTTP“Location”HTTP响应标头字段中包含片段组成部分。这些客户端需要使用除了3xx重定向响应以外的其他方法来重定向客户端——-例如,返回一个HTML页面,其中包含一个具有链接到重定向URI的动作的“继续”按钮。

客户端必须忽略无法识别的响应参数。本规范未定义授权码字符串大小。客户端应该避免假设代码值的长度。 授权服务器应记录其发放的任何值的大小。

  • 4.2.2.1.错误响应

    4.2.2.1. 错误响应

    如果由于缺失、无效或不匹配的重定向URI而请求失败,或者如果客户端表示缺失或无效,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

如果资源所有者拒绝访问请求,或者如果请求因为其他非缺失或无效重定向URI原因而失败,授权服务器通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的片段部分添加下列参数通知客户端:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含无效的参数值、包含一个参数超过一次或其他不良格式。
    • unauthorized_client
      客户端未被授权使用此方法请求授权码。
    • access_denied
      资源所有者或授权服务器拒绝该请求。
    • unsupported_response_type
      授权服务器不支持使用此方法获得授权码。
    • invalid_scope
      请求的范围无效,未知的或格式不正确。
    • server_error
      授权服务器遇到意外情况导致其无法执行该请求。(此错误代码是必要的,因为500内部服务器错误HTTP状态代码不能由HTTP重定向返回给客户端)。
    • temporarily_unavailable
      授权服务器由于暂时超载或服务器维护目前无法处理请求。 (此错误代码是必要的,因为503服务不可用HTTP状态代码不可以由HTTP重定向返回给客户端)。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。

    “error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。

    “error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。

  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz

4.3. 资源所有者密码凭据许可

资源所有者密码凭据许可类型适合于资源所有者与客户端具有信任关系的情况,如设备操作系统或高级特权应用。当启用这种许可类型时授权服务器应该特别关照且只有当其他流程都不可用时才可以。

这种许可类型适合于能够获得资源所有者凭据(用户名和密码,通常使用交互的形式)的客户端。通过转换已存储的凭据至访问令牌,它也用于迁移现存的使用如HTTP基本或摘要身份验证的直接身份验证方案的客户端至OAuth。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      v
      |    Resource Owner
     (A) Password Credentials
      |
      v
 +---------+                                  +---------------+
 |         |>--(B)---- Resource Owner ------->|               |
 |         |         Password Credentials     | Authorization |
 | Client  |                                  |     Server    |
 |         |<--(C)---- Access Token ---------<|               |
 |         |    (w/ Optional Refresh Token)   |               |
 +---------+                                  +---------------+

图5:资源所有者密码凭据流程

图5中的所示流程包含以下步骤:

  • (A)资源所有者提供给客户端它的用户名和密码。
  • (B)通过包含从资源所有者处接收到的凭据,客户端从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进行身份验证。
  • (C)授权服务器对客户端进行身份验证,验证资源所有者的凭证,如果有效,颁发访问令牌。

  • 4.3.1.授权请求和响应

  • 4.3.2.访问令牌请求
  • 4.3.3.访问令牌响应

    4.3.1. 授权请求和响应

    客户端获得资源所有者凭据所通过的方式超出了本规范的范围。一旦获得访问令牌,客户端必须丢弃凭据。

    4.3.2. 访问令牌请求

    客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:
  • grant_type
    必需的。值必须设置为“password”。
  • username
    必需的。资源所有者的用户名。
  • password
    必需的。资源所有者的密码。
  • scope
    可选的。如3.3节所述的访问请求的范围。
    如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如3.2.1)节所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份,并
  • 使用它现有的密码验证算法验证资源所有者的密码凭据。

由于这种访问令牌请求使用了资源所有者的密码,授权服务器必须保护端点防止暴力攻击(例如,使用速率限制或生成警报)。

4.3.3. 访问令牌响应

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。
一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

4.4. 客户端凭据许可

当客户端请求访问它所控制的,或者事先与授权服务器协商(所采用的方法超出了本规范的范围)的其他资源所有者的受保护资源,客户端可以只使用它的客户端凭据(或者其他受支持的身份验证方法)请求访问令牌。

客户端凭据许可类型必须只能由机密客户端使用。

 +---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

图6:客户端凭证流程

图6中的所示流程包含以下步骤:

  • (A)客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。
  • (B)授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。

  • 4.4.1.授权请求和响应

  • 4.4.2.访问令牌请求
  • 4.4.3.访问令牌响应

    4.4.1. 授权请求和响应

    由于客户端身份验证被用作授权许可,所以不需要其他授权请求。

    4.4.2. 访问令牌请求

    客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:
  • grant_type
    必需的。值必须设置为“client_credentials”。
  • scope
    可选的。如3.3节所述的访问请求的范围。

客户端必须如3.2.1所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials

授权服务器必须对客户端进行身份验证。

4.4.3. 访问令牌响应

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。刷新令牌不应该包含在内。 如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600, "example_parameter":"example_value"
}

4.5. 扩展许可

通过使用绝对URI作为令牌端点的“grant_type”参数的值指定许可类型,并通过添加任何其他需要的参数,客户端使用扩展许可类型。

例如,采用[OAuth-SAML]定义的安全断言标记语言(SAML)2.0断言许可类型请求访问令牌,客户端可以使用TLS发起如下的HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2bearer&assertion=PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU[...为简洁起见省略...]aG5TdGF0ZW1lbnQ-PC9Bc3NlcnRpb24-

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

5. 颁发访问令牌

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

  • 5.1.成功响应
  • 5.2.错误响应

    5.1. 成功的响应

    授权服务器颁发访问令牌和可选的刷新令牌,通过向HTTP响应实体正文中添加下列参数并使用200(OK)状态码构造响应:
  • access_token
    必需的。授权服务器颁发的访问令牌。
  • token_type
    必需的。如7.1节所述的颁发的令牌的类型。值是大小写敏感的。
  • expires_in
    推荐的。以秒为单位的访问令牌生命周期。例如,值“3600”表示访问令牌将在从生成响应时的1小时后到期。如果省略,则授权服务器应该通过其他方式提供过期时间,或者记录默认值。
  • refresh_token
    可选的。刷新令牌,可以用于如第6节所述使用相同的授权许可获得新的访问令牌。
  • scope
    可选的,若与客户端请求的范围相同;否则,必需的。如3.3节所述的访问令牌的范围。

这些参数使用RFC4627定义的“application/json”媒体类型包含在HTTP响应实体正文中。通过将每个参数添加到最高结构级别, 参数被序列化为JavaScript对象表示法(JSON)的结构。参数名称和字符串值作为JSON字符串类型包含。数值的值作为JSON数字类型包含。参数顺序无关并可以变化。

在任何包含令牌、凭据或其他敏感信息的响应中,授权服务器必须在其中包含值为“no-store”的HTTP“Cache-Control”响应头部域RFC2616,和值为“no-cache”的“Pragma”响应头部域RFC2616。例如:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

客户端必须忽略响应中不能识别的值的名称。令牌和从授权服务器接收到的值的大小未定义。客户端应该避免对值的大小做假设。授权服务器应记录其发放的任何值的大小。

5.2. 错误响应

授权服务器使用HTTP 400(错误请求)状态码响应,在响应中包含下列参数:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含不支持的参数值(除了许可类型)、重复参数、包含多个凭据、采用超过一种客户端身份验证机制或其他不规范的格式。
    • invalid_client
      客户端身份验证失败(例如,未知的客户端,不包含客户端身份验证,或不支持的身份验证方法)。授权服务器可以返回HTTP 401(未授权)状态码来指出支持的HTTP身份验证方案。如果客户端试图通过“Authorization”请求标头域进行身份验证,授权服务器必须响应HTTP 401(未授权)状态码,并包含与客户端使用的身份验证方案匹配的“WWW-Authenticate”响应标头字段。
    • invalid_grant
      提供的授权许可(如授权码、资源所有者凭据)或刷新令牌无效、过期、吊销、与在授权请求使用的重定向URI不匹配或颁发给另一个客户端。
    • unauthorized_client
      进行身份验证的客户端没有被授权使用这种授权许可类型。
    • unsupported_grant_type
      授权许可类型不被授权服务器支持。
    • invalid_scope
      请求的范围无效、未知的、格式不正确或超出资源所有者许可的范围。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。“error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。
  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。“error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。

这些参数使用RFC4627定义的“application/json”媒体类型包含在HTTP响应实体正文中。通过将每个参数添加到最高结构级别, 参数被序列化为JavaScript对象表示法(JSON)的结构。参数名称和字符串值作为JSON字符串类型包含。数值的值作为JSON数字类型包含。参数顺序无关并可以变化。例如:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "error":"invalid_request"
}

6. 刷新访问令牌

若授权服务器给客户端颁发了刷新令牌,客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起刷新请求:

  • grant_type
    必需的。值必须设置为“refresh_token”。
  • refresh_token
    必需的。颁发给客户端的刷新令牌。
  • scope
    可选的。如3.3节所述的访问请求的范围。请求的范围不能包含任何不是由资源所有者原始许可的范围,若省略,被视为与资源所有者原始许可的范围相同。

因为刷新令牌通常是用于请求额外的访问令牌的持久凭证,刷新令牌绑定到被它被颁发给的客户端。如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如3.2.1节所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份并确保刷新令牌是被颁发给进行身份验证的客户端的,并
  • 验证刷新令牌。

如果有效且被授权,授权服务器如5.1节所述颁发访问令牌。如果请求因验证失败或无效,授权服务器5.2节所述返回错误响应。

授权服务器可以颁发新的刷新令牌,在这种情况下,客户端必须放弃旧的刷新令牌,替换为新的刷新令牌。在向客户端颁发新的刷新令牌后授权服务器可以撤销旧的刷新令牌。若颁发了新的刷新令牌,刷新令牌的范围必须与客户端包含在请求中的刷新令牌的范围相同。

7. 访问受保护资源

通过向资源服务器出示访问令牌,客户端访问受保护资源。资源服务器必须验证访问令牌,并确保它没有过期且其范围涵盖了请求的资源。资源服务器用于验证访问令牌的方法(以及任何错误响应)超出了本规范的范围,但一般包括资源服务器和授权服务器之间的互动或协调。

客户端使用访问令牌与资源服务器进行证认的方法依赖于授权服务器颁发的访问令牌的类型。通常,它涉及到使用具有所采用的访问令牌类型的规范定义的身份验证方案(如RFC6750)的HTTP“Authorization”的请求标头字段RFC2617

7.1. 访问令牌类型

访问令牌的类型给客户端提供了成功使用该访问令牌(和类型指定的属性)发起受保护资源请求所需的信息 若客户端不理解令牌类型,则不能使用该访问令牌。

例如,RFC6750定义的“bearer”令牌类型简单的在请求中包含访问令牌字符串来使用:

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer F_9.B5f-4.1JqM

而[OAuth-HTTP-MAC]定义的“mac”令牌类型通过与许可类型一起颁发用于对HTTP请求中某些部分签名的消息认证码(MAC)的密钥来使用。

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",nonce="274312:dj83hs9s",mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

提供上面的例子仅作说明用途。建议开发人员在使用前查阅RFC6750和[OAuth-HTTP-MAC]规范。

每一种访问令牌类型的定义指定与“access_token”响应参数一起发送到客户端的额外属性。它还定义了HTTP验证方法当请求受保护资源时用于包含访问令牌。

7.2. 错误响应

如果资源访问请求失败,资源服务器应该通知客户端该错误。虽然规定这些错误响应超出了本规范的范围,但是本文档在11.4节建立了一张公共注册表,用作OAuth令牌身份验证方案之间分享的错误值。

主要为OAuth令牌身份验证设计的新身份验证方案应该定义向客户端提供错误状态码的机制,其中允许的错误值限于本规范建立的错误注册表中。

这些方案可以限制有效的错误代码是注册值的子集。如果错误代码使用命名参数返回,该参数名称应该是“error”。

其他能够被用于OAuth令牌身份验证的方案,但不是主要为此目的而设计的,可以帮顶他们的错误值到相同方式的注册表项。

新的认证方案也可以选择指定使用“error_description”和"error_uri"参数,用于以本文档中用法相同的方式的返回错误信息。

8. 可扩展性

采用URI命名的类型应该限定于特定供应商的实现,它们不是普遍适用的并且特定于使用它们的资源服务器的实现细节。

所有其他类型都必须注册。类型名称必需符合type-name ANBF。如果类型定义包含了一种新的HTTP身份验证方案,该类型名称应该与该HTTP身份验证方案名称一致(如RFC2617定义)。令牌类型“example”被保留用于样例中。

type-name  = 1*name-char
name-char  = "-" / "." / "_" / DIGIT / ALPHA

8.2. 定义新的端点参数

用于授权端点或令牌端点的新的请求或响应参数按照11.2节中的过程在OAuth参数注册表中定义和注册。

参数名称必须符合param-name ABNF,并且参数值的语法必须是明确定义的(例如,使用ABNF,或现有参数的语法的引用)。

param-name  = 1*name-char
name-char   = "-" / "." / "_" / DIGIT / ALPHA

不是普遍适用的并且特定于使用它们的授权服务器的实现细节的未注册的特定供应商的参数扩展应该采用特定供应商的前缀(例如,以“companyname_”开头),从而不会与其他已注册的值冲突。

8.3. 定义新的授权许可类型

新的授权许可类型可以通过赋予它们一个“grant_type”参数使用的唯一的绝对URI来定义。如果扩展许可类型需要其他令牌端点参数,它们必须如11.2节所述在OAuth参数注册表中注册。

8.4. 定义新的授权端点响应类型

用于授权端点的新的响应类型按照11.3节中的过程在授权端点响应类型注册表中定义和注册。响应类型名称必须符合response-type ABNF。

response-type  = response-name *( SP response-name )
response-name  = 1*response-char
response-char  = "_" / DIGIT / ALPHA

如果响应类型包含一个或多个空格字符(%x20),它被看作是一个空格分隔的值列表,其中的值的顺序不重要。只有一种值的顺序可以被注册,它涵盖了相同的值的集合的所有其他排列。

例如,响应类型“token code”未由本规范定义。然而,一个扩展可以定义和注册“token code”响应类型。 一旦注册,相同的组合“code token”不能被注册,但是这两个值都可以用于表示相同的响应类型。

8.5. 定义其他错误代码

在协议扩展(例如,访问令牌类型、扩展参数或扩展许可类型等)需要其他错误代码用于授权码许可错误响应(4.1.2.1节)、隐式许可错误响应(4.2.2.1节)、令牌错误响应(5.2节)或资源访问错误响应(7.2节)的情况下,这些错误代码可以被定义。

如果用于与它们配合的扩展是已注册的访问令牌类型,已注册的端点参数或者扩展许可类型,扩展错误代码必须被注册。用于未注册扩展的错误代码可以被注册。

错误代码必须符合的error ABNF,且可能的话应该以一致的名称作前缀。例如,一个表示给扩展参数“example”设置了无效值的错误应该被命名为“example_invalid”。

 error      = 1*error-char
 error-char = %x20-21 / %x23-5B / %x5D-7E

9. 本机应用程序

本机应用程序是安装和执行在资源所有者使用的设备上的客户端(例如,桌面程序,本机移动应用)。本机应用程序需要关于安全、平台能力和整体最终用户体验的特别注意事项。

授权端点需要在客户端和资源所有者用户代理之间进行交互。本机应用程序可以调用外部的用户代理,或在应用程序中嵌入用户代理。例如:

  • 外部用户代理-本机应用程序可以捕获来自授权服务器的响应。它可以使用带有操作系统已注册方案的重定向URI调用客户端作为处理程序,手动复制粘贴凭据,运行本地Web服务器,安装用户代理扩展,或者通过提供重定向URI来指定客户端控制下的服务器托管资源,反过来使响应对本机应用程序可用。
  • 嵌入式用户代理-通过监视资源加载过程中发生的状态变化或者访问用户代理的cookies存储,本机应用程序直接与嵌入式用户代理通信,获得响应。
    当在外部或嵌入式用户代理中选择时,开发者应该考虑如下:
  • 外部用户代理可能会提高完成率,因为资源所有者可能已经有了与授权服务器的活动会话,避免了重新进行身份验证的需要。它提供了熟悉的最终用户体验和功能。资源所有者可能也依赖于用户代理特性或扩展帮助他进行身份验证(例如密码管理器、两步设备读取器)
  • 嵌入式用户代理可能会提供更好的可用性,因为它避免了切换上下文和打开新窗口的需要。
  • 嵌入式用户代理构成了安全挑战,因为资源所有者在一个未识别的窗口中进行身份验证,无法获得在大多数外部用户代理中的可视的保护。嵌入式用户代理教育用户信任未标识身份验证请求(使钓鱼攻击更易于实施)。
    当在隐式许可类型和授权码许可类型中选择时,下列应该被考虑:
  • 使用授权码许可类型的本机应用程序应该这么做而不需使用用户凭据,因为本机应用程序无力保持客户端凭据的机密性。
  • 当使用隐式许可类型流程时,刷新令牌不会返回,这就要求一旦访问令牌过期就要重复授权过程。

10. 安全考量

作为一个灵活的可扩展的框架,OAuth的安全性考量依赖于许多因素。 以下小节提为实现者提供了聚焦在2.1节所述的三种客户端配置上的安全指南:Web应用、基于用户代理的应用和本地应用程序。

全面的OAuth安全模型和分析以及该协议设计的背景在[OAuth-THREATMODE]中提供。

授权不得向本地应用程序或基于用户代理的应用客户端颁发客户端密码或其他客户端凭据用于客户端验证目的。授权服务器可以颁发客户端密码或其他凭据给专门的设备上特定安装的本地应用程序客户端。

当客户端身份验证不可用时,授权服务器应该采用其他方式来验证客户端的身份-例如,通过要求客户端重定向URI的注册或者引入资源所有者来确认身份。当请求资源所有者授权时,有效的重定向URI是不足以验证客户端的身份,但可以用来防止在获得资源所有者授权后将凭据传递给假冒的客户端。

授权服务器必须考虑与未进行身份验证的客户端交互的安全实现并采取措施限制颁发给这些客户端的其他凭据(如刷新令牌)的潜在泄露。

10.2. 客户端仿冒

如果被仿冒的客户端不能,或无法保持其客户端凭据保密。恶意客户端可能冒充其他客户端,并获得对受保护资源的访问权限。

授权服务器任何可能的时候必须验证客户端身份。如果授权服务器由于客户端的性质无法对客户端进行身份验证,授权服务器必须要求注册任何用于接收授权响应的重定向URI并且应该利用其他手段保护资源所有者防止这样的潜在仿冒客户端。例如,授权服务器可以引入资源所有者来帮助识别客户端和它的来源。

授权服务器应该实施显式的资源所有者身份验证并且提供给资源所有者有关客户端及其请求的授权范围和生命周期的信息。由资源所有者在当前客户端上下文中审查信息并授权或拒绝该请求。

授权服务器未对客户端进行身份验证(没有活动的资源所有者交互)或未依靠其他手段确保重复的请求来自于原始客户端而非冒充者时,不应该自动处理重复的授权请求。

10.3. 访问令牌

访问令牌凭据(以及任何机密的访问令牌属性)在传输和储存时必须保持机密性,并只与授权服务器、访问令牌生效的资源服务器和访问令牌被颁发的客户端共享。访问令牌凭据必须只能使用带有RFC2818定义的服务器身份验证的1.6节所述的TLS 传输。

当使用隐式授权许可类型时,访问令牌在URI片段中传输,这可能泄露访问令牌给未授权的一方。

授权服务器必须确保访问令牌不能被生成、修改或被未授权一方猜测而产生有效的访问令牌。

客户端应该为最小范围的需要请求访问令牌。授权服务器在选择如何兑现请求的范围时应该将客户端身份考虑在内,且可以颁发具有比请求的更少的权限的访问令牌。

本规范未给资源服务器提供任何方法来确保特定的客户端提交给它的访问令牌是授权服务器颁发给此客户端的。

10.4. 刷新令牌

授权服务器可以给Web应用客户端和本机应用程序客户端颁发刷新令牌。

刷新令牌在传输和储存时必须保持机密性,并只与授权服务器和刷新令牌被颁发的客户端共享。授权服务器必须维护刷新令牌和它被颁发给的客户端之间的绑定。刷新令牌必须只能使用带有RFC2818定义的服务器身份验证的1.6所述的TLS 传输。
授权服务器必须验证刷新令牌和客户端身份之间的绑定,无论客户端身份是否能被验证。当无法进行客户端身份验证时,授权服务器应该采取其他手段检测刷新令牌滥用。

例如,授权服务器可以使用刷新令牌轮转机制,随着每次访问令牌刷新响应,新的刷新令牌被颁发。以前的刷新令牌被作废但是由授权服务器保留。如果刷新令牌被泄露,随后同时被攻击者和合法客户端使用,他们中一人将提交被作废的刷新令牌,这将通知入侵给授权服务器。

授权服务器必须确保刷新令牌不能被生成、修改或被未授权一方猜测而产生有效的刷新令牌。

10.5. 授权码

授权码的传输应该建立在安全通道上,客户端应该要求在它的重定向URI上使用TLS,若该URI指示了一个网络资源。 由于授权码由用户代理重定向传输,它们可能潜在地通过用户代理历史记录和HTTP参照标头被泄露。

授权码明以纯文本承载凭据使用,用于验证在授权服务器许可权限的资源所有者就是返回到客户端完成此过程的相同的资源所有者。因此,如果客户端依赖于授权码作为它自己的资源所有者身份验证,客户端重定向端点必须要求使用TLS。

授权码必须是短暂的且是单用户的。如果授权服务器观察到多次以授权码交换访问令牌的尝试,授权服务器应该试图吊销所有基于泄露的授权码而颁发的访问令牌。

如果客户端可以进行身份验证,授权服务器必须验证客户端身份,并确保授权码颁发给了同一个客户端。

10.6. 授权码重定向URI伪造

当使用授权码许可类型请求授权时,客户端可以通过“redirect_uri”参数指定重定向URI。 如果攻击者能够伪造重定向URI的值,这可能导致授权服务器向攻击者控制的URI重定向带有授权码的资源所有者用户代理。

攻击者可以在合法客户端上创建一个帐户,并开始授权流程。当攻击者的用户代理被发送到授权服务器来许可访问权限时,攻击者抓取合法客户端提供的授权URI并用攻击者控制下的URI替换客户端的重定向URI。 攻击者然后欺骗受害者顺着仿冒的链接来对合法客户端授权访问权限。

一旦在授权服务器——受害者被唆使代表一个合法的被信任的客户端使用正常有效的请求——授权该请求时。受害者然后带着授权码重定向到受攻击者控制的端点。通过使用客户端提交的原始重定向URI向客户端发送授权码,攻击者完成授权流程。客户端用授权码交换访问令牌并与将它与攻击者的客户端账号关联,该账户现在能获得受害者授权的(通过客户端)对访问受保护资源的访问权限。

为了防止这种攻击,授权服务器必须确保用于获得授权码的重定向URI与当用授权码交换访问令牌时提供的重定向URI相同。授权服务器必须要求公共客户端,并且应该要求机密客户注册它们的重定向URI。如果在请求中提供一个重定向URI,授权服务器必须验证对注册的值。如果在请求中提供了重定向URI,授权服务器必须对比已注册的。
10.7. 资源所有者密码凭据

资源所有者密码凭据许可类型通常用于遗留或迁移原因。它降低了由客户端存储用户名和密码的整体风险,但并没有消除泄露高度特权的凭证给客户端的需求。

这种许可类型比其他许可类型承载了更高的风险,因为它保留了本协议寻求避免的密码反模式。客户端可能滥用密码或密码可能会无意中被泄露给攻击者(例如,通过客户端保存的日志文件或其他记录)。

此外,由于资源拥有者对授权过程没有控制权(在转手它的凭据给客户端后资源所有者的参与结束),客户端可以获得比资源所有者预期的具有更大范围的访问令牌。授权服务器应该考虑由这种许可类型颁发的访问令牌的范围和寿命。

授权服务器和客户端应该尽量减少这种许可类型的使用,并尽可能采用其他许可类型。

10.8. 请求机密性

访问令牌、刷新令牌、资源所有者密码和客户端凭据不能以明文传输。授权码不应该以明文传输。

“state”和“scope”参数不应该包含敏感的客户端或资源所有者的纯文本信息,因为它们可能在不安全的通道上被传输或被不安全地存储。

10.9. 确保端点真实性

为了防止中间人攻击,授权服务器必须对任何被发送到授权和令牌端点的请求要求RFC2818中定义的具有服务器身份验证的TLS 的使用。客户端必须按RFC6125定义且按照它服务器身份进行身份验证的需求验证授权服务器的的TLS证书。

10.10. 凭据猜测攻击

授权服务器必须防止攻击者猜测访问令牌、授权码、刷新令牌、资源所有者密码和客户端凭据。

攻击者猜测已生成令牌(和其它不打算被最终用户掌握的凭据)的概率必须小于或等于2 ^(-128),并且应该小于或等于2 ^(-160)。

授权服务器必须采用其他手段来保护打算给最终用户使用的凭据。

10.11. 钓鱼攻击

本协议或类似协议的广泛部署,可能导致最终用户变成习惯于被重定向到要求输入他们的密码的网站的做法。

如果最终用户在输入他们的凭据前不注意辨别这些网站的真伪,这将使攻击者利用这种做法窃取资源所有者的密码成为可能。

服务提供者应尝试教育最终用户有关钓鱼攻击构成的风险,并且应该为最终用户提供使确认它们的站点的真伪变得简单的机制。客户端开发者应该考虑他们如何与用户代理(例如,外部的和嵌入式的)交互的安全启示以及最终用户辨别授权服务器真伪的能力。

为了减小钓鱼攻击的风险,授权服务器必须要求在用于最终用户交互的每个端点上使用TLS。

10.12. 跨站请求伪造

跨站请求伪造(CSRF)是一种漏洞利用,攻击者致使受害的最终用户按恶意URI(例如以误导的链接、图片或重定向提供给用户代理)到达受信任的服务器(通常由存在有效的会话Cookie而建立)。

针对客户端的重定向URI的CSRF攻击允许攻击者注入自己的授权码或访问令牌,这将导致在客户端中使用与攻击者的受保护资源关联的访问令牌而非受害者的(例如,保存受害者的银行账户信息到攻击者控制的受保护资源)。

客户端必须为它的重定向URI实现CSRF保护。这通常通过要求向重定向URI端点发送的任何请求包含该请求对用户代理身份认证状态的绑定值(例如,用于对用户代理进行身份验证的会话Cookie的哈希值)来实现。客户端应该使用“state”请求参数在发起授权请求时向授权服务器传送该值。

一旦从最终用户获得授权,授权服务器重定向最终用户的用户代理带着要求的包含在“state”参数中的绑定值回到客户端。 通过该绑定值与用户代理的身份验证状态的匹配,绑定值使客户端能够验证请求的有效性。用于CSRF保护的绑定值必须包含不可猜测的值(如10.10节所述)且用户代理的身份验证状态(例如会话Cookie、HTML5本地存储)必须保存在只能被客户端和用户代理访问的地方(即通过同源策略保护)。

针对授权服务器的授权端点的CSRF攻击可能导致攻击者获得最终用户为恶意客户端的授权而不牵涉或警告最终用户。

授权服务器必须为它的授权端点实现CSRF保护并且确保在资源所有者未意识到且无显式同意时恶意客户端不能获得授权。

10.13. 点击劫持

在点击劫持攻击中,攻击者注册一个合法客户端然后构造一个恶意站点,在一个透明的覆盖在一组虚假按钮上面的嵌入框架中加载授权服务器的授权端点Web页面,这些按钮被精心构造恰好放置在授权页面上的重要按钮下方。当最终用户点击了一个误导的可见的按钮时,最终用户实际上点击了授权页面上一个不可见的按钮(例如“授权”按钮)。 这允许攻击者欺骗资源所有者许可它的客户端最终用户不知晓的访问权限。

为了防止这种形式的攻击,在请求最终用户授权时本机应用程序应该使用外部浏览器而非应用程序中嵌入的浏览器。 对于大多数较新的浏览器,避免嵌入框架可以由授权服务器使用(非标准的)“x-frame-options”标头实施。 该标头可以有两个值,“deny”和“sameorigin”,它将阻止任何框架,或按不同来源的站点分别构造框架。 对于较旧的浏览器,JavaScript框架破坏技术可以使用,但可能不会在所有的浏览器中生效。

10.14. 代码注入和输入验证

代码注入攻击当程序使用的输入或其他外部变量未清洗而导致对程序逻辑的修改时发生。 这可能允许攻击者对应用程序的设备或它的数据的访问权限,导致服务拒绝或引入许多的恶意副作用。

授权服务器和客户端必须清洗(并在可能的情况下验证)收到的任何值--特别是,“state”和“redirect_uri”参数的值。

10.15. 自由重定向器

授权服务器、授权端点和客户端重定向端点可能被不当配置,被作为自由重定向器。自由重定向器是一个使用参数自动地向参数值指定而无任何验证的地址重定向用户代理的端点。

自由重定向器可被用于钓鱼攻击,或者被攻击者通过使用熟悉的受信任的目标地址的URI授权部分使最终用户访问恶意站点。此外,如果授权服务器允许客户端只注册部分的重定向URI,攻击者可以使用客户端操作的自由重定向器构造重定向URI,这将跳过授权服务器验证但是发送授权码或访问令牌给攻击者控制下的端点。

10.16. 在隐式流程中滥用访问令牌假冒资源所有者

对于使用隐式流程的公共客户端,本规范没有为客户端提供任何方法来决定访问令牌颁发给的是什么样的客户端。

资源所有者可能通过给攻击者的恶意客户端许可访问令牌自愿委托资源的访问权限。这可能是由于钓鱼或一些其他借口。攻击者也可能通过其他机制窃取令牌。 攻击者然后可能会尝试通过向合法公开客户端提供该访问令牌假冒资源拥有者。

在隐式流程(response_type=token)中,攻击者可以轻易转换来自授权服务器的响应中的令牌,用事先颁发给攻击者的令牌替换真实的访问令牌。

依赖于在返回通道中传递访问令牌识别客户端用户的与本机应用程序通信的服务器可能由攻击者创建能够注入随意的窃取的访问令牌的危险的程序被类似地危及。

任何做出只有资源所有者能够提交给它有效的为资源的访问令牌的假设的公共客户端都是易受这种类型的攻击的。

这种类型的攻击可能在合法的客户端上泄露有关资源所有者的信息给攻击者(恶意客户端)。这也将允许攻击者在合法客户端上用和资源所有者相同的权限执行操作,该资源所有者最初许可了访问令牌或授权码。

客户端对资源拥有者进行身份验证超出了本规范的范围。任何使用授权过程作为客户端对受委托的最终用户进行身份验证的形式的规范(例如,第三方登录服务)不能在没有其他的客户端能够判断访问令牌是否颁发是颁发给它使用的安全机制的情况下使用隐式流程(例如,限制访问令牌的受众)。

11. IANA考量

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册访问令牌类型。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“访问令牌类型example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.1.1. 注册模板

  • Type name:

    请求的名称(例如,“example”)。

  • Additional Token Endpoint Response Parameters:

    随“access_token”参数一起返回的其他响应参数。 新的参数都必须如11.2节所述在OAuth参数注册表中分别注册。

  • HTTP Authentication Scheme(s):

    HTTP身份验证方案名称,如果有的话,用于使用这种类型的访问令牌对受保护资源进行身份验证。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。 对于其他,给出负责的部分的名称。 其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。 相关章节的指示也可以包含但不是必需的。

    11.2. OAuth参数注册表

    本规范建立OAuth参数注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册列入授权端点请求、授权端点响应、令牌端点请求或令牌端点响应的其他参数。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如,参数“example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.2.1. 注册模板

  • Parameter name:

    请求的名称(例如,“example”)。

  • Parameter usage location:

    参数可以使用的位置。 可能的位置为授权请求、授权响应、令牌请求或令牌响应。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的。

    11.2.2. 最初的注册表内容

    OAuth参数注册表中的初始内容:

  • Parameter name: client_id
  • Parameter usage location: authorization request, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: client_secret
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: response_type
  • Parameter usage location: authorization request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: redirect_uri
  • Parameter usage location: authorization request, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: scope
  • Parameter usage location: authorization request, authorization response, token request, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: state
  • Parameter usage location: authorization request, authorization response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: code
  • Parameter usage location: authorization response, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: error_description
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: error_uri
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: grant_type
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: access_token
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: token_type
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: expires_in
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: username
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: password
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: refresh_token
  • Parameter usage location: token request, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749

11.3. OAuth授权端点响应类型注册表

本规范建立OAuth授权端点响应类型注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册授权端点使用的其他响应类型。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“响应类型example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.3.1. 注册模板

  • Response type name:

    请求的名称(例如,“example”)。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的

    11.3.2. 最初的注册表内容

    OAuth授权端点响应类型注册表的初始内容:

  • Response type name: code
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Response type name: token
  • Change controller: IETF
  • Specification document(s):RFC 6749

11.4. OAuth扩展错误注册表

本规范建立OAuth扩展错误注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册与其他协议扩展(例如,扩展的许可类型、访问令牌类型或者扩展参数)一起使用的其他错误代码。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“错误代码example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.4.1. 注册模板

  • Error name:

    请求的名称(例如,“example”)。错误名称的值
    不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • Error usage location:

    错误使用的位置。可能的位置是授权代码许可错误响应(4.1.2.1节),隐式许可错误响应(4.2.2.1节),令牌错误响应(5.2节),或资源访问错误的响应(7.2节)。

  • Related protocol extension:

    与错语代码一起使用的扩展许可类型、访问令牌类型或扩展参数的名称。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的。

12. 参考文献

  • 12.1.规范性文献
  • 12.2.参考性文献

    12.1. 规范性参考文件

  • RFC2119
    Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14,RFC 2119, March 1997.
  • RFC2246
    Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", RFC 2246, January 1999.
  • RFC2616
    Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
  • RFC2617
    Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999.
  • RFC2818
    Rescorla, E., "HTTP Over TLS", RFC 2818, May 2000.
  • [RFC3629]
    Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.
  • RFC3986
    Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, January 2005.
  • RFC4627
    Crockford, D., "The application/json Media Type for JavaScript Object Notation (JSON)", RFC 4627, July 2006.
  • RFC4949
    Shirey, R., "Internet Security Glossary, Version 2", RFC 4949, August 2007.
  • RFC5226
    Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26,RFC 5226, May 2008.
  • RFC5234
    Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, January 2008.
  • RFC5246
    Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, August 2008.
  • RFC6125
    Saint-Andre, P. and J. Hodges, "Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)", RFC 6125, March 2011.
  • [USASCII]
    American National Standards Institute, "Coded Character Set -- 7-bit American Standard Code for Information Interchange", ANSI X3.4, 1986.
  • [W3C.REC-html401-19991224]
    Raggett, D., Le Hors, A., and I. Jacobs, "HTML 4.01 Specification", World Wide Web Consortium Recommendation REC-html401-19991224, December 1999,http://www.w3.org/TR/1999/REC-html401-19991224.
  • [W3C.REC-xml-20081126]
    Bray, T., Paoli, J., Sperberg-McQueen, C., Maler, E., and F. Yergeau, "Extensible Markup Language (XML) 1.0 (Fifth Edition)", World Wide Web Consortium Recommendation REC-xml-20081126, November 2008,http://www.w3.org/TR/2008/REC-xml-20081126.

    12.2. 参考性引用文献

  • [OAuth-HTTP-MAC]
    Hammer-Lahav, E., Ed., "HTTP Authentication: MAC Access Authentication", Work in Progress, February 2012.
  • [OAuth-SAML2]
    Campbell, B. and C. Mortimore, "SAML 2.0 Bearer Assertion Profiles for OAuth 2.0", Work in Progress, September 2012.
  • [OAuth-THREATMODEL]
    Lodderstedt, T., Ed., McGloin, M., and P. Hunt, "OAuth 2.0 Threat Model and Security Considerations", Work in Progress, October 2012.
  • [OAuth-WRAP]
    Hardt, D., Ed., Tom, A., Eaton, B., and Y. Goland, "OAuth Web Resource Authorization Profiles", Work in Progress, January 2010.
  • RFC5849
    Hammer-Lahav, E., "The OAuth 1.0 Protocol", RFC 5849, April 2010.
  • RFC6750
    Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, October 2012.
]]>
转自RFC 6749-OAuth 2.0授权框架简体中文翻译

1. 简介

在传统的客户端-服务器身份验证模式中,客户端请求服务器上限制访问的资源(受保护资源)时,需要使用资源所有者的凭据在服务器上进行身份验证。
资源所有者为了给第三方应用提供受限资源的访问,需要与第三方共享它的凭据。 这造成一些问题和局限:

  • 需要第三方应用存储资源所有者的凭据,以供将来使用,通常是明文密码。
  • 需要服务器支持密码身份认证,尽管密码认证天生就有安全缺陷。
  • 第三方应用获得的资源所有者的受保护资源的访问权限过于宽泛,从而导致资源所有者失去对资源使用时限或使用范围的控制。
  • 资源所有者不能仅撤销某个第三方的访问权限而不影响其它,并且,资源所有者只有通过改变第三方的密码,才能单独撤销这第三方的访问权限。
  • 与任何第三方应用的让步导致对终端用户的密码及该密码所保护的所有数据的让步。

OAuth通过引入授权层以及分离客户端角色和资源所有者角色来解决这些问题。
在OAuth中,客户端在请求受资源所有者控制并托管在资源服务器上的资源的访问权限时,将被颁发一组不同于资源所有者所拥有凭据的凭据。

客户端获得一个访问令牌(一个代表特定作用域、生命期以及其他访问属性的字符串),用以代替使用资源所有者的凭据来访问受保护资源。
访问令牌由授权服务器在资源所有者认可的情况下颁发给第三方客户端。客户端使用访问令牌访问托管在资源服务器的受保护资源。

例如,终端用户(资源所有者)可以许可一个打印服务(客户端)访问她存储在图片分享网站(资源服务器)上的受保护图片,而无需与打印服务分享自己的用户名和密码。
反之,她直接与图片分享网站信任的服务器(授权服务器)进行身份验证,该服务器颁发给打印服务具体委托凭据(访问令牌)。

本规范是为HTTP(RFC2616)协议量身定制。在任何非HTTP协议上使用OAuth不在本规范的范围之内。

OAuth 1.0协议(RFC5849)作为一个指导性文档发布,是一个小社区的工作成果。
本标准化规范在OAuth 1.0的部署经验之上构建,也包括其他使用案例以及从更广泛的IETF社区收集到的可扩展性需求。
OAuth 2.0协议不向后兼容OAuth 1.0。这两个版本可以在网络上共存,实现者可以选择同时支持他们。
然而,本规范的用意是新的实现支持按本文档指定的Auth 2.0,OAuth 1.0仅用于支持现有的部署。
OAuth 2.0协议与OAuth 1.0协议实现细节没有太多关联。熟悉OAuth 1.0的实现者应该学习本文档,而不对有关OAuth 2.0的结构和细节做任何假设。

Links

1.1. 角色

OAuth定义了四种角色:

  • 资源所有者

    能够许可受保护资源访问权限的实体。当资源所有者是个人时,它作为最终用户被提及。

  • 资源服务器

    托管受保护资源的服务器,能够接收和响应使用访问令牌对受保护资源的请求。

  • 客户端

    使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。术语“客户端”并非特指任何特定的的实现特点(例如:应用程序是否在服务器、台式机或其他设备上执行)。

  • 授权服务器

    在成功验证资源所有者且获得授权后颁发访问令牌给客户端的服务器。
    授权服务器和资源服务器之间的交互超出了本规范的范围。授权服务器可以和资源服务器是同一台服务器,也可以是分离的个体。一个授权服务器可以颁发被多个资源服务器接受的访问令牌。

Links

1.2. 协议流程

 +--------+                               +---------------+
 |        |--(A)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(B)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(C)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(D)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(E)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(F)--- Protected Resource ---|               |
 +--------+                               +---------------+

图1:抽象的协议流程

图1中所示的抽象OAuth 2.0流程描述了四个角色之间的交互,包括以下步骤:

  • (A)客户端向从资源所有者请求授权。授权请求可以直接向资源所有者发起(如图所示),或者更可取的是通过作为中介的授权服务器间接发起。
  • (B)客户端收到授权许可,这是一个代表资源所有者的授权的凭据,使用本规范中定义的四种许可类型之一或 者使用扩展许可类型表示。授权许可类型取决于客户端请求授权所使用的方式以及授权服务器支持的类型。
  • (C)客户端与授权服务器进行身份认证并出示授权许可请求访问令牌。
  • (D)授权服务器验证客户端身份并验证授权许可,若有效则颁发访问令牌。
  • (E)客户端从资源服务器请求受保护资源并出示访问令牌进行身份验证。
  • (F)资源服务器验证访问令牌,若有效则满足该请求。

客户端用于从资源所有者获得授权许可(步骤(A)和(B)所示)的更好方法是使用授权服务器作为中介,如4.1节图3所示。

Links

1.3. 授权许可

授权许可是一个代表资源所有者授权(访问受保护资源)的凭据,客户端用它来获取访问令牌。本规范定义了四种许可类型——授权码、隐式许可、资源所有者密码凭据和客户端凭据——以及用于定义其他类型的可扩展性机制。

Links

1.3.1. 授权码

授权码通过使用授权服务器做为客户端与资源所有者的中介而获得。客户端不是直接从资源所有者请求授权,而是引导资源所有者至授权服务器(由在RFC2616中定义的用户代理),授权服务器之后引导资源所有者带着授权码回到客户端。

在引导资源所有者携带授权码返回客户端前,授权服务器会鉴定资源所有者身份并获得其授权。由于资源所有者只与授权服务器进行身份验证,所以资源所有者的凭据不需要与客户端分享。

授权码提供了一些重要的安全益处,例如验证客户端身份的能力,以及向客户端直接的访问令牌的传输而非通过资源所有者的用户代理来传送它而潜在暴露给他人(包括资源所有者)。

Links

1.3.2. 隐式许可

隐式许可是为用如JavaScript等脚本语言在浏览器中实现的客户端而优化的一种简化的授权码流程。在隐式许可流程中,不再给客户端颁发授权码,取而代之的是客户端直接被颁发一个访问令牌(作为资源所有者的授权)。这种许可类型是隐式的,因为没有中间凭据(如授权码)被颁发(之后用于获取访问令牌)。

当在隐式许可流程中颁发访问令牌时,发授权服务器不对客户端进行身份验证。在某些情况下,客户端身份可以通过用于向客户端传送访问令牌的重定向URI验证。访问令牌可能会暴露给资源所有者,或者对资源所有者的用户代理有访问权限的其他应用程序。

隐式许可提高了一些客户端(例如一个作为浏览器内应用实现的客户端)的响应速度和效率,因为它减少了获取访问令牌所需的往返数量。然而,这种便利应该和采用隐式许可的安全影响作权衡,如那些在10.310.16节中所述的,尤其是当授权码许可类型可用的时候。

Links

1.3.3. 资源所有者密码凭据

资源所有者密码凭据(即用户名和密码),可以直接作为获取访问令牌的授权许可。这种凭据只能应该当资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序),以及当其他授权许可类型(例如授权码)不可用时被使用。

尽管本授权类型需要对资源所有者凭据直接的客户端访问权限,但资源所有者凭据仅被用于一次请求并被交换为访问令牌。通过凭据和长期有效的访问令牌或刷新令牌的互换,这种许可类型可以消除客户端存储资源所有者凭据供将来使用的需要。

Links

1.3.4. 客户端凭据

当授权范围限于客户端控制下的受保护资源或事先与授权服务器商定的受保护资源时客户端凭据可以被用作为一种授权许可。典型的当客户端代表自己表演(客户端也是资源所有者)或者基于与授权服务器事先商定的授权请求对受保护资源的访问权限时,客户端凭据被用作为授权许可。

Links

1.4. 访问令牌

访问令牌是用于访问受保护资源的凭据。访问令牌是一个代表向客户端颁发的授权的字符串。该字符串通常对于客户端是不透明的。令牌代表了访问权限的由资源所有者许可并由资源服务器和授权服务器实施的具体范围和期限。

令牌可以表示一个用于检索授权信息的标识符或者可以以可验证的方式自包含授权信息(即令牌字符串由数据和签名组成)。额外的身份验证凭据——在本规范范围以外——可以被要求以便客户端使用令牌。

访问令牌提供了一个抽象层,用单一的资源服务器能理解的令牌代替不同的授权结构(例如,用户名和密码)。这种抽象使得颁发访问令牌比颁发用于获取令牌的授权许可更受限制,同时消除了资源服务器理解各种各样身份认证方法的需要。

基于资源服务器的安全要求访问令牌可以有不同的格式、结构及采用的方法(如,加密属性)。访问令牌的属性和用于访问受保护资源的方法超出了本规范的范围,它们在RFC6750等配套规范中定义。

Links

1.5. 刷新令牌

访问令牌是用于获取访问令牌的凭据。刷新令牌由授权服务器颁发给客户端,用于在当前访问令牌失效或过期时,获取一个新的访问令牌,或者获得相等或更窄范围的额外的访问令牌(访问令牌可能具有比资源所有者所授权的更短的生命周期和更少的权限)。颁发刷新令牌是可选的,由授权服务器决定。如果授权服务器颁发刷新令牌,在颁发访问令牌时它被包含在内(即图1中的步骤D)。

刷新令牌是一个代表由资源所有者给客户端许可的授权的字符串。该字符串通常对于客户端是不透明的。该令牌表示一个用于检索授权信息的标识符。不同于访问令牌,刷新令牌设计只与授权服务器使用,并不会发送到资源服务器。

+--------+                                           +---------------+
|        |--(A)------- Authorization Grant --------->|               |
|        |                                           |               |
|        |<-(B)----------- Access Token -------------|               |
|        |               & Refresh Token             |               |
|        |                                           |               |
|        |                            +----------+   |               |
|        |--(C)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(D)- Protected Resource --| Resource |   | Authorization |
| Client |                            |  Server  |   |     Server    |
|        |--(E)---- Access Token ---->|          |   |               |
|        |                            |          |   |               |
|        |<-(F)- Invalid Token Error -|          |   |               |
|        |                            +----------+   |               |
|        |                                           |               |
|        |--(G)----------- Refresh Token ----------->|               |
|        |                                           |               |
|        |<-(H)----------- Access Token -------------|               |
+--------+           & Optional Refresh Token        +---------------+

图2:刷新过期的访问令牌

图2中的所示流程包含以下步骤:

  • (A)客户端通过与授权服务器进行身份验证并出示授权许可请求访问令牌。
  • (B)授权服务器对客户端进行身份验证并验证授权许可,若有效则颁发访问令牌和刷新令牌。
  • (C)客户端通过出示访问令牌向资源服务器发起受保护资源的请求。
  • (D)资源服务器验证访问令牌,若有效则满足该要求。
  • (E)步骤(C)和(D)重复进行,直到访问令牌到期。如果客户端知道访问令牌已过期,跳到步骤(G),否 则它将继续发起另一个对受保护资源的请求。
  • (F)由于访问令牌是无效的,资源服务器返回无效令牌错误。
  • (G)客户端通过与授权服务器进行身份验证并出示刷新令牌,请求一个新的访问令牌。客户端身份验证要求基于客户端的类型和授权服务器的策略。
  • (H)授权服务器对客户端进行身份验证并验证刷新令牌,若有效则颁发一个新的访问令牌(和——可选地——一个新的刷新令牌)。

步骤(C)、(D)、(E)和(F)在本规范的范围以外,如第7节中所述。

Links

1.6. TLS版本

本规范任何时候使用传输层安全性(TLS),基于广泛的部署和已知的安全漏洞TLS的相应版本(或多个版本)将会随时间而变化。在本规范撰写时,TLS 1.2版RFC5246是最新的版本,但它具有非常局限的部署基础,可能还未准备好可以实现。TLS 1.0版RFC2246是部署最广泛的版本并将提供最宽泛的互操作性。

实现也可以支持满足其安全需求的其他传输层安全机制。

Links

1.7. HTTP重定向

本规范广泛采用了HTTP重定向,有此客户端或授权服务器引导资源所有者的用户代理到另一个目的地址。虽然本规范中的例子演示了HTTP 302状态码的使用,但是任何其他通过用户代理完成重定向的方法都是允许的并被考虑作为实现细节。

Links

1.8. 互操作性

OAuth 2.0提供了丰富的具有明确的安全性质的授权框架。然而,尽管在其自身看来是一个带有许多可选择组件的丰富且高度可扩展的框架,本规范有可能产生许多非可互操作的实现。

此外,本规范中留下一些必需组件部分或完全没有定义(例如,客户端注册、授权服务器性能、端点发现等)。没有这些组件,客户端必须针对特定的授权服务器和资源服务器被手动并专门配置,以进行互操作。

本框架设计具有一个明确的期望,即未来工作将定义实现完整的web范围的互操作性所需的规范性的配置和扩展。

Links

1.9. 符号约定

本规范中的关键词“必须”、“不能”、“必需的”、“要”、“不要”、“应该”、“不应该”、“推荐的”、“可以”以及“可选的”按RFC2119所述解释。
本规范使用RFC5234的扩展巴科斯-诺尔范式(ABNF)表示法。此外,来自“统一资源标识符(URI):通用语法”RFC3986的规则URI引用也包含在内。

某些安全相关的术语按照RFC4949中定义的意思理解。这些术语包括但不限于:“攻击”、“身份认证”、“授权”、“证书”、“机密”,“凭据”,“加密”,“身份”,“记号”,“签名”,“信任”,“验证”和“核实”。

除非另有说明,所有协议参数的名称和值是大小写敏感的。

Links

2.0 客户端注册

在开始协议前,客户端在授权服务器注册。客户端在授权服务器上注册所通过的方式超出了本规范,但典型的涉及到最终用户与HTML注册表单的交互。

客户端注册不要求客户端与授权服务器之间的直接交互。在授权服务器支持时,注册可以依靠其他方式来建立信任关系并获取客户端的属性(如重定向URI、客户端类型)。例如,注册可以使用自发行或第三方发行声明或通过授权服务器使用信任通道执行客户端发现完成。

当注册客户端时,客户端开发者应该:

  • 指定2.1节所述的客户端类型,
  • 提供它的如3.1.2节所述的客户端重定向URI,且
  • 包含授权服务器要求的任何其他信息(如,应用名称、网址、描述、Logo图片、接受法律条款等)。
  • 2.1.客户端类型
  • 2.2.客户端标识
  • 2.3.客户端身份验证
  • 2.3.1.客户端密码
  • 2.3.2.其他身份验证方法
  • 2.4.未注册的客户端

    2.1. 客户端类型

    根据客户端与授权服务器安全地进行身份验证的能力(即维护客户端凭据机密性的能力),OAuth定义了两种客户端类型:
  • 机密客户端
    能够维持其凭据机密性(如客户端执行在具有对客户端凭据有限访问权限的安全的服务器上),或者能够使用 其他方式保证客户端身份验证的安全性。
  • 公开客户端
    不能够维持其凭据的机密性(如客户端执行在由资源所有者使用的设备上,例如已安装的本地应用程序或基于Web浏览器的应用),且不能通过其他方式保证客户端身份验证的安全性。
    客户端类型的选择基于授权服务器的安全身份认证定义以及其对客户端凭据可接受的暴露程度。授权服务器不应该对客户端类型做假设。

客户端可以以分布式的组件集合实现,每一个组件具有不同的客户端类型和安全上下文(例如,一个同时具有机密的基于服务器的组件和公开的基于浏览器的组件的分布式客户端)。如果授权服务器不提供对这类客户端的支持,或不提供其注册方面的指导,客户端应该注册每个组件为一个单独的客户端。
本规范围绕下列客户端配置涉及:

  • Web应用程序
    Web应用是一个运行在Web服务器上的机密客户端。资源所有者通过其使用的设备上的用户代理里渲染的HTML用户界面访问客户端。客户端凭据以及向客户端颁发的任何访问令牌都存储在Web服务器上且不会暴露给资源所有者或者被资源所有者可访问。
  • 基于用户代理的应用
    基于用户代理的应用是一个公开客户端,客户端的代码从Web服务器下载,并在资源所有者使用的设备上的用户代理(如Web浏览器)中执行。协议数据和凭据对于资源所有者是可轻易访问的(且经常是可见的)。由于这些应用驻留在用户代理内,在请求授权时它们可以无缝地使用用户代理的功能。
  • 本机应用程序
    本机应用是一个安装、运行在资源所有者使用的设备上的公开客户端。协议数据和凭据对于资源所有者是可访问的。假定包含在应用程序中的任何客户端身份认证凭据可以被提取。另一方面,动态颁发的如访问令牌或者刷新令牌等凭据可以达到可接受的保护水平。至少,这些凭据被保护不被应用可能与之交互的恶意服务器接触。在一些平台上,这些凭证可能被保护免于被驻留在相同设备上的其他应用接触。

    2.2. 客户端标识

    授权服务器颁发给已注册客户端客户端标识——一个代表客户端提供的注册信息的唯一字符串。客户端标识不是一个秘密,它暴露给资源所有者并且不能单独用于客户端身份验证。客户端标识对于授权服务器是唯一的。

客户端的字符串大小本规范未定义。客户端应该避免对标识大小做假设。授权服务器应记录其发放的任何标识的大小。

2.3. 客户端身份验证

如果客户端类型是机密的,客户端和授权服务器建立适合于授权服务器的安全性要求的客户端身份验证方法。授权服务器可以接受符合其安全要求的任何形式的客户端身份验证。

机密客户端通常颁发(或建立)一组客户端凭据用于与授权服务器进行身份验证(例如,密码、公/私钥对)。授权服务器可以与公共客户端建立客户端身份验证方法。然而,授权服务器不能依靠公共客户端身份验证达到识别客户端的目的。

客户端在每次请求中不能使用一个以上的身份验证方法。

  • 2.3.1.客户端密码
  • 2.3.2.其他身份验证方法

    2.3.1. 客户端密码

    拥有客户端密码的客户端可以使用RFC2617中定义的HTTP基本身份验证方案与授权服务器进行身份验证。客户端标识使用按照附录B的“application/x-www-form-urlencoded”编码算法编码,编码后的值用作用户名;客户端密码使用相同的算法编码并用作密码。授权服务器必须支持HTTP基本身份验证方案,用于验证被颁发客户端密码的客户端的身份。例如(额外的换行仅用于显示目的):

    Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

此外,授权服务器可以使用下列参数支持在请求正文中包含客户端凭据:

  • client_id
    必需的。如2.2节所述的注册过程中颁发给客户端的客户端标识。
  • client_secret
    必需的。客户端秘密。 客户端可以忽略该参数若客户端秘密是一个空字符串。

使用这两个参数在请求正文中包含客户端凭据是不被建议的,应该限于不能直接采用HTTP基本身份验证方案(或其他基于密码的HTTP身份验证方案)的客户端。参数只能在请求正文中传送,不能包含在请求URI中。

例如,使用请求正文参数请求刷新访问令牌(第6节)(额外的换行仅用于显示目的):

 POST /token HTTP/1.1
 Host: server.example.com
 Content-Type: application/x-www-form-urlencoded
 grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&client_id=s6BhdRkqt3&client_secret=7Fjfp0ZBr1KtDRbnfVdmIw

当使用密码身份验证发送请求时,授权服务器必须要求使用如1.6所述的TLS。

由于该客户端身份验证方法包含密码,授权服务器必须保护所有使用到密码的端点免受暴力攻击。

2.3.2. 其他身份验证方法

授权服务器可以支持任何与其安全要求匹配的合适的HTTP身份验证方案。当使用其他身份验证方法时,授权服务器必须定义客户端标识(注册记录)和认证方案之间的映射。

2.4. 未注册客户端

本规范不排除使用未注册的客户端。然而,使用这样的客户端超出了本规范的范围,并需要额外的安全性分析并审查其互操作性影响。

3. 协议端点

授权过程采用了两种授权服务器端点(HTTP资源):

  • 授权端点——客户端用其通过用户代理重定向从资源所有者获取授权。
  • 令牌端点——客户端用其将授权许可交换为访问令牌,通常伴有客户端身份验证。

以及一种客户端端点:

  • 重定向端点——授权服务器用其通过资源所有者用户代理向客户端返回含有授权凭据的响应。

并不是每种授权许可类型都采用两种端点。

扩展许可类型可以按需定义其他端点。

客户端通过何种方式获得授权端点的位置超出了本文档范围,但该位置通常在服务文档中提供。

端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

由于向授权端点的请求引起用户身份验证和明文凭据传输(在HTTP响应中),当向授权端点发送请求时,授权服务器必须要求如1.6节所述的TLS的使用。

授权服务器对于授权端点必须支持使用HTTP“GET”方法RFC2616,也可以支持使用“POST”的方法。

发送的没有值的参数必须被对待为好像它们在请求中省略了。授权服务器必须忽略不能识别的请求参数。 请求和响应参数不能包含超过一次。

3.1.1. 响应类型

授权端点被授权码许可类型和隐式许可类型流程使用。客户端使用下列参数通知授权服务器期望的许可类型:

  • response_type
    必需的。其值必须是如4.1.1节所述用于请求授权码的“code”,如4.2.1节所述用于请求访问令牌的“token”(隐式许可)或者如8.4节所述的一个注册的扩展值之中的一个。

扩展响应类型可以包含一个空格(%x20)分隔的值的列表,值的顺序并不重要(例如,响应类型“a b”与“b a”相同)。 这样的复合响应类型的含义由他们各自的规范定义。

如果授权请求缺少“response_type”参数,或者如果响应类型不被理解,授权服务器必须返回一个4.1.2.1所述的错误响应。

3.1.2. 重定向端点

在完成与资源所有者的交互后,授权服务器引导资源所有者的用户代理返回到客户端。授权服务器重定向用户代理至客户端的重定向端点,该端点是事先在客户端注册过程中或者当发起授权请求时与授权服务器建立的。

重定向端点URI必须是如RFC3986的3.4节所述的绝对URI。端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

3.1.2.1. 端点请求的机密性

当所请求的响应类型是“code”或“token”时,或者当重定向请求将引起在蜜柑凭据通过公开网络传输时,重定向端点应该要求使用1.6节所述的TLS。本规范没有强制使用TLS,因为在撰写本规范时,要求客户端部署TLS对于许多客户端开发者是一严重的困难。如果TLS不可用,授权服务器应该在重定向之前警告资源所有者有关非安全端点(例如,在授权请求期间现实一条信息)。

缺乏传输层安全可能对客户端及它被授权访问的受保护资源的安全具有严重影响。当授权过程用作一种客户端委托的对最终用户认证(例如,第三方登录服务)的形式时,使用传输层安全尤其关键。

3.1.2.2. 注册要求

授权服务器必须要求下列客户端注册它们的重定向端点:

  • 公开客户端。
  • 采用隐式许可类型的机密客户端。

授权服务器应该要求所有客户端在使用授权端点前注册它们的重定向端点。

授权服务器应该要求客户端提供完整的重定向URI(客户端可以使用“state”请求参数实现每请求自定义)。如果要求完整的重定向URI注册不可行,授权服务器应该要求注册URI方案、授权和路径(当请求授权时只允许客户端动态改变重定向URI的查询部分)。

授权服务器可以允许客户端注册多个重定向端点。

缺少重定向URI注册的要求,可能使攻击者如10.15所述将授权端点用作自由重定向端点。

3.1.2.3. 动态配置

如果多个重定向URI被注册,或者如果只有部分重定向URI被注册,或者如果没有重定向URI被注册,客户端都必须使用“redirect_uri”请求参数在授权请求中包含重定向URI。

当重定向URI被包含在授权请求中时,若有任何重定向URI被注册,授权服务器必须将接收到的值与至少一个已注册的重定向URI(或URI部分)按RFC3986第6节所述进行比较并匹配。如果客户端注册包含了完整的重定向URI,授权服务器必须使用RFC3986第6.2.1节中定义的简单字符串比较法比对这两个URI 。

3.1.2.4. 无效端点

如果由于缺失、无效或不匹配的重定向URI而验证失败,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

3.1.2.5. 端点内容

向客户端端点的重定向请求通常会引起由用户代理处理的HTML文档响应。如果HTML响应直接作为重定向请求的服务结果,任何包含在HTML文档中的脚本将执行,并具有对重定向URI和其包含的凭据的完全访问权限。

客户端不应该在重定向端点的响应中包含任何第三方的脚本(例如,第三方分析、社交插件、广告网络)。相反,它应该从URI中提取凭据并向另一个端点重定向用户代理而不暴露凭据(在URI中或其他地方)。如果包含第三方脚本,客户端必须确保它自己的脚本(用于从URI中提取凭据并从URI中删除)将首先执行。

3.2. 令牌端点

客户端通过提交它的授权许可或刷新令牌使用令牌端点获取访问令牌。令牌端点被用于除了隐式许可类型(因为访问令牌是直接颁发的)外的每种授权许可中。

客户端通过何种方式获得令牌端点的位置超出了本文档范围,但该位置通常在服务文档中提供。

端点URI可以包含“application/x-www-form-urlencoded”格式(按附录B)的查询部分(RFC3986的3.4节),当添加额外的查询参数时必须保留该部分。端点URI不得包含片段部分。

由于向令牌端点的请求引起明文凭据的传输(在HTTP请求和响应中),当向令牌端点发送请求时,授权服务器必须要求如1.6节所述的TLS的使用。

当发起访问令牌请求时,客户端必须使用HTTP“POST”方法。

发送的没有值的参数必须被对待为好像它们在请求中省略了。授权服务器必须忽略不能识别的请求参数。请求和响应参数不能包含超过一次。

3.2.1. 客户端身份验证

在向令牌端点发起请求时,机密客户端或其他被颁发客户端凭据的客户端必须如2.3节所述与授权服务器进行身份验证。客户端身份验证用于:

  • 实施刷新令牌和授权码到它们被颁发给的客户端的绑定。当授权码在不安全通道上向重定向端点传输时,或者 当重定向URI没有被完全注册时,客户端身份验证是关键的。
  • 通过禁用客户端或者改变其凭据从被入侵的客户端恢复,从而防止攻击者滥用被盗的刷新令牌。改变单套客户端凭据显然快于撤销一整套刷新令牌。
  • 实现身份验证管理的最佳实践,要求定期凭证轮转。轮转一整套刷新令牌可能是艰巨的,而轮转单组客户端凭据显然更容易。

在向令牌端点发送请求时,客户端可以使用“client_id”请求参数标识自己。向令牌端点的“authorization_code”和“grant_type”请求中,未经身份验证的客户端必须发送它的“client_id”,以防止自己无意中接受了本打算给具有另一个“client_id”的客户端的代码。这保护了客户端免于被替换认证码。(它没有对手保护起源提供额外的安全。)

3.3. 访问令牌范围

授权端点和令牌端点允许客户端使用“scope”请求参数指定访问请求的范围。反过来,授权服务器使用“scope”响应参数通知客户端颁发的访问令牌的范围。

范围参数的值表示为以空格分隔,大小写敏感的字符串。 由授权服务器定义该字符串。如果该值包含多个空格分隔的字符串,他们的顺序并不重要且每个字符串为请求的范围添加一个额外的访问区域。

scope = scope-token *( SP scope-token )
scope-token = 1*( %x21 / %x23-5B / %x5D-7E )

基于授权服务器的策略或资源拥有者的指示,授权服务器可以全部或部分地忽略客户端请求的范围。如果颁发的访问令牌范围和客户端请求的范围不同,授权服务器必须包含“scope”响应参数通知客户端实际许可的范围。

在请求授权时如果客户端忽略了范围参数,授权服务器必须要么使用预定义的默认值处理请求,要么使请求失败以指出无效范围。授权服务器应该记录它的范围需求和默认值(如果已定义)。

4. 获得授权

为了请求访问令牌,客户端从资源所有者获得授权。授权表现为授权许可的形式,客户端用它请求访问令牌。OAuth定义了四种许可类型:授权码、隐式许可、资源所有者密码凭据和客户端凭据。它还提供了扩展机制定义其他许可类型。

在图3中所示的流程包括以下步骤:

  • (A)客户端通过向授权端点引导资源所有者的用户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,一旦访问被许可(或拒绝)授权服务器将传送用户代理回到该URI。
  • (B)授权服务器验证资源拥有者的身份(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
  • (C)假设资源所有者许可访问,授权服务器使用之前(在请求时或客户端注册时)提供的重定向URI重定向用户代理回到客户端。重定向URI包括授权码和之前客户端提供的任何本地状态。
  • (D)客户端通过包含上一步中收到的授权码从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进行身份验证。客户端包含用于获得授权码的重定向URI来用于验证。
  • (E)授权服务器对客户端进行身份验证,验证授权代码,并确保接收的重定向URI与在步骤(C)中用于重定向客户端的URI相匹配。如果通过,授权服务器响应返回访问令牌与可选的刷新令牌。

  • 4.1.1.授权请求

  • 4.1.2.授权响应
  • 4.1.3.访问令牌请求
  • 4.1.4.访问令牌响应

    4.1.1. 授权请求

    客户端通过按附录B使用“application/x-www-form-urlencoded”格式向授权端点URI的查询部分添加下列参数构造请求URI:
  • response_type
    必需的。值必须被设置为“code”。
  • client_id
    必需的。如2.2节所述的客户端标识。
  • redirect_uri
    可选的。如3.1.2节所述。
  • scope
    可选的。如3.3节所述的访问请求的范围。
  • state
    推荐的。客户度用于维护请求和回调之间的状态的不透明的值。当重定向用户代理回到客户端时,授权服务器包含此值。该参数应该用于防止如10.12所述的跨站点请求伪造。

客户端使用HTTP重定向响应向构造的URI定向资源所有者,或者通过经由用户代理至该URI的其他可用方法。
例如,客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

授权服务器验证该请求,确保所有需要的参数已提交且有效。如果请求是有效的,授权服务器对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过经由其他方式确定批准)。

当确定决定后,授权服务器使用HTTP重定向响应向提供的客户端重定向URI定向用户代理,或者通过经由用户代理至该URI的其他可行方法。

4.1.2. 授权响应

如果资源所有者许可访问请求,授权服务器颁发授权码,通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的查询部分添加下列参数传递授权码至客户端:

  • code
    必需的。授权服务器生成的授权码。授权码必须在颁发后很快过期以减小泄露风险。推荐的最长的授权码生命周期是10分钟。客户端不能使用授权码超过一次。如果一个授权码被使用一次以上,授权服务器必须拒绝该请求并应该撤销(如可能)先前发出的基于该授权码的所有令牌。授权码与客户端标识和重定向URI绑定。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

客户端必须忽略无法识别的响应参数。本规范未定义授权码字符串大小。客户端应该避免假设代码值的长度。授权服务器应记录其发放的任何值的大小。

  • 4.1.2.1.错误响应

    4.1.2.1. 错误响应

    如果由于缺失、无效或不匹配的重定向URI而请求失败,或者如果客户端表示缺失或无效,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

如果资源所有者拒绝访问请求,或者如果请求因为其他非缺失或无效重定向URI原因而失败,授权服务器通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的查询部分添加下列参数通知客户端:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含无效的参数值、包含一个参数超过一次或其他不良格式。
    • unauthorized_client
      客户端未被授权使用此方法请求授权码。
    • access_denied
      资源所有者或授权服务器拒绝该请求。
    • unsupported_response_type
      授权服务器不支持使用此方法获得授权码。
    • invalid_scope
      请求的范围无效,未知的或格式不正确。
    • server_error
      授权服务器遇到意外情况导致其无法执行该请求。(此错误代码是必要的,因为500内部服务器错误HTTP状态代码不能由HTTP重定向返回给客户端)。
    • temporarily_unavailable
      授权服务器由于暂时超载或服务器维护目前无法处理请求。(此错误代码是必要的,因为503服务不可用HTTP状态代码不可以由HTTP重定向返回给客户端)。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。
    “error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。
  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。
    “error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?error=access_denied&state=xyz

4.1.3. 访问令牌请求

客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:

  • grant_type
    必需的。值必须被设置为“authorization_code”。
  • code
    从授权服务器收到的授权码。
  • redirect_uri
    必需的,若“redirect_uri”参数如4.1.1节所述包含在授权请求中,且他们的值必须相同。
  • client_id
    必需的,如果客户端没有如3.2.1节所述与授权服务器进行身份认证。

如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如
必需的,如果客户端没有如3.2.1节所述与授权服务器进行身份验证。

例如,客户端使用TLS发起如下的HTTP请求(额外的换行符仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份,
  • 确保授权码颁发给了通过身份验证的机密客户端,或者如果客户端是公开的,确保代码颁发给了请求中的“client_id”,
  • 验证授权码是有效的,并
  • 确保给出了“redirect_uri”参数,若“redirect_uri”参数如4.1.1所述包含在初始授权请求中,且若包含,确保它们的值是相同的。

    4.1.4. 访问令牌响应

    如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

4.2. 隐式许可

隐式授权类型被用于获取访问令牌(它不支持发行刷新令牌),并对知道操作具体重定向URI的公共客户端进行优化。这些客户端通常在浏览器中使用诸如JavaScript的脚本语言实现。

由于这是一个基于重定向的流程,客户端必须能够与资源所有者的用户代理(通常是Web浏览器)进行交互并能够接收来自授权服务器的传入请求(通过重定向)。

不同于客户端分别请求授权和访问令牌的授权码许可类型,客户端收到访问令牌作为授权请求的结果。

隐式许可类型不包含客户端身份验证而依赖于资源所有者在场和重定向URI的注册。因为访问令牌被编码到重定向URI中,它可能会暴露给资源所有者和其他驻留在相同设备上的应用。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      ^
      |
     (B)
 +----|-----+          Client Identifier     +---------------+
 |         -+----(A)-- & Redirection URI --->|               |
 |  User-   |                                | Authorization |
 |  Agent  -|----(B)-- User authenticates -->|     Server    |
 |          |                                |               |
 |          |<---(C)--- Redirection URI ----<|               |
 |          |          with Access Token     +---------------+
 |          |            in Fragment
 |          |                                +---------------+
 |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
 |          |          without Fragment      |     Client    |
 |          |                                |    Resource   |
 |     (F)  |<---(E)------- Script ---------<|               |
 |          |                                +---------------+
 +-|--------+
   |    |
  (A)  (G) Access Token
   |    |
   ^    v
 +---------+
 |         |
 |  Client |
 |         |
 +---------+

注:说明步骤(A)和(B)的直线因为通过用户代理而被分为两部分。

图4:隐式许可流程

图4中的所示流程包含以下步骤:

  • (A)客户端通过向授权端点引导资源所有者的用户代理开始流程。客户端包括它的客户端标识、请求范围、本地状态和重定向URI,一旦访问被许可(或拒绝)授权服务器将传送用户代理回到该URI。
  • (B)授权服务器验证资源拥有者的身份(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
  • (C)假设资源所有者许可访问,授权服务器使用之前(在请求时或客户端注册时)提供的重定向URI重定向用户代理回到客户端。重定向URI在URI片段中包含访问令牌。
  • (D)用户代理顺着重定向指示向Web托管的客户端资源发起请求(按RFC2616该请求不包含片段)。用户代理在本地保留片段信息。
  • (E)Web托管的客户端资源返回一个网页(通常是带有嵌入式脚本的HTML文档),该网页能够访问包含用户代理保留的片段的完整重定向URI并提取包含在片段中的访问令牌(和其他参数)。
  • (F)用户代理在本地执行Web托管的客户端资源提供的提取访问令牌的脚本。
  • (G)用户代理传送访问令牌给客户端。

参见1.3.2节和第9节了解使用隐式许可的背景。

参见10.3节和10.16节了解当使用隐式许可时的重要安全注意事项。

4.2.1. 授权请求

客户端通过按附录B使用“application/x-www-form-urlencoded”格式向授权端点URI的查询部分添加下列参数构造请求URI:

  • response_type
    必需的。值必须设置为“token”。
  • client_id
    必需的。如2.2节所述的客户端标识。
  • redirect_uri
    可选的。如3.1.2节所述。
  • scope
    可选的。如3.3节所述的访问请求的范围。
  • state
    推荐的。客户度用于维护请求和回调之间的状态的不透明的值。当重定向用户代理回到客户端时,授权服务器包含此值。该参数应该用于防止如10.12所述的跨站点请求伪造。

客户端使用HTTP重定向响应向构造的URI定向资源所有者,或者通过经由用户代理至该URI的其他可用方法。

例如,客户端使用TLS定向用户代理发起下述HTTP请求(额外的换行仅用于显示目的):

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

授权服务器验证该请求,确保所有需要的参数已提交且有效。授权服务器必须验证它将重定向访问令牌的重定向URI与如3.1.2节所述的客户端注册的重定向URI匹配。

如果请求是有效的,授权服务器对资源所有者进行身份验证并获得授权决定(通过询问资源所有者或通过经由其他方式确定批准)。

当确定决定后,授权服务器使用HTTP重定向响应向提供的客户端重定向URI定向用户代理,或者通过经由用户代理至该URI的其他可行方法。

4.2.2. 访问令牌响应

如果资源所有者许可访问请求,授权服务器颁发访问令牌,通过使用按附录B的“application/x-www-form-urlencoded”格式向重定向URI的片段部分添加下列参数传递访问令牌至客户端:

  • access_token
    必需的。授权服务器颁发的访问令牌。
  • token_type
    必需的。如7.1节所述的颁发的令牌的类型。值是大小写敏感的。
  • expires_in
    推荐的。以秒为单位的访问令牌生命周期。例如,值“3600”表示访问令牌将在从生成响应时的1小时后到期。如果省略,则授权服务器应该通过其他方式提供过期时间,或者记录默认值。
  • scope
    可选的,若与客户端请求的范围相同;否则,是必需的。如3.3节所述的访问令牌的范围。
  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。授权服务器不能颁发刷新令牌。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:(额外的换行符仅用于显示目的):

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600

开发人员应注意,一些用户代理不支持在HTTP“Location”HTTP响应标头字段中包含片段组成部分。这些客户端需要使用除了3xx重定向响应以外的其他方法来重定向客户端——-例如,返回一个HTML页面,其中包含一个具有链接到重定向URI的动作的“继续”按钮。

客户端必须忽略无法识别的响应参数。本规范未定义授权码字符串大小。客户端应该避免假设代码值的长度。 授权服务器应记录其发放的任何值的大小。

  • 4.2.2.1.错误响应

    4.2.2.1. 错误响应

    如果由于缺失、无效或不匹配的重定向URI而请求失败,或者如果客户端表示缺失或无效,授权服务器应该通知资源所有者该错误且不能向无效的重定向URI自动重定向用户代理。

如果资源所有者拒绝访问请求,或者如果请求因为其他非缺失或无效重定向URI原因而失败,授权服务器通过按附录B使用“application/x-www-form-urlencoded”格式向重定向URI的片段部分添加下列参数通知客户端:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含无效的参数值、包含一个参数超过一次或其他不良格式。
    • unauthorized_client
      客户端未被授权使用此方法请求授权码。
    • access_denied
      资源所有者或授权服务器拒绝该请求。
    • unsupported_response_type
      授权服务器不支持使用此方法获得授权码。
    • invalid_scope
      请求的范围无效,未知的或格式不正确。
    • server_error
      授权服务器遇到意外情况导致其无法执行该请求。(此错误代码是必要的,因为500内部服务器错误HTTP状态代码不能由HTTP重定向返回给客户端)。
    • temporarily_unavailable
      授权服务器由于暂时超载或服务器维护目前无法处理请求。 (此错误代码是必要的,因为503服务不可用HTTP状态代码不可以由HTTP重定向返回给客户端)。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。

    “error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。

    “error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。

  • state
    必需的,若“state”参数在客户端授权请求中提交。从客户端接收的精确值。

例如,授权服务器通过发送以下HTTP响应重定向用户代理:

HTTP/1.1 302 Found
Location: https://client.example.com/cb#error=access_denied&state=xyz

4.3. 资源所有者密码凭据许可

资源所有者密码凭据许可类型适合于资源所有者与客户端具有信任关系的情况,如设备操作系统或高级特权应用。当启用这种许可类型时授权服务器应该特别关照且只有当其他流程都不可用时才可以。

这种许可类型适合于能够获得资源所有者凭据(用户名和密码,通常使用交互的形式)的客户端。通过转换已存储的凭据至访问令牌,它也用于迁移现存的使用如HTTP基本或摘要身份验证的直接身份验证方案的客户端至OAuth。

 +----------+
 | Resource |
 |  Owner   |
 |          |
 +----------+
      v
      |    Resource Owner
     (A) Password Credentials
      |
      v
 +---------+                                  +---------------+
 |         |>--(B)---- Resource Owner ------->|               |
 |         |         Password Credentials     | Authorization |
 | Client  |                                  |     Server    |
 |         |<--(C)---- Access Token ---------<|               |
 |         |    (w/ Optional Refresh Token)   |               |
 +---------+                                  +---------------+

图5:资源所有者密码凭据流程

图5中的所示流程包含以下步骤:

  • (A)资源所有者提供给客户端它的用户名和密码。
  • (B)通过包含从资源所有者处接收到的凭据,客户端从授权服务器的令牌端点请求访问令牌。当发起请求时,客户端与授权服务器进行身份验证。
  • (C)授权服务器对客户端进行身份验证,验证资源所有者的凭证,如果有效,颁发访问令牌。

  • 4.3.1.授权请求和响应

  • 4.3.2.访问令牌请求
  • 4.3.3.访问令牌响应

    4.3.1. 授权请求和响应

    客户端获得资源所有者凭据所通过的方式超出了本规范的范围。一旦获得访问令牌,客户端必须丢弃凭据。

    4.3.2. 访问令牌请求

    客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:
  • grant_type
    必需的。值必须设置为“password”。
  • username
    必需的。资源所有者的用户名。
  • password
    必需的。资源所有者的密码。
  • scope
    可选的。如3.3节所述的访问请求的范围。
    如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如3.2.1)节所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份,并
  • 使用它现有的密码验证算法验证资源所有者的密码凭据。

由于这种访问令牌请求使用了资源所有者的密码,授权服务器必须保护端点防止暴力攻击(例如,使用速率限制或生成警报)。

4.3.3. 访问令牌响应

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。
一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

4.4. 客户端凭据许可

当客户端请求访问它所控制的,或者事先与授权服务器协商(所采用的方法超出了本规范的范围)的其他资源所有者的受保护资源,客户端可以只使用它的客户端凭据(或者其他受支持的身份验证方法)请求访问令牌。

客户端凭据许可类型必须只能由机密客户端使用。

 +---------+                                  +---------------+
 |         |                                  |               |
 |         |>--(A)- Client Authentication --->| Authorization |
 | Client  |                                  |     Server    |
 |         |<--(B)---- Access Token ---------<|               |
 |         |                                  |               |
 +---------+                                  +---------------+

图6:客户端凭证流程

图6中的所示流程包含以下步骤:

  • (A)客户端与授权服务器进行身份验证并向令牌端点请求访问令牌。
  • (B)授权服务器对客户端进行身份验证,如果有效,颁发访问令牌。

  • 4.4.1.授权请求和响应

  • 4.4.2.访问令牌请求
  • 4.4.3.访问令牌响应

    4.4.1. 授权请求和响应

    由于客户端身份验证被用作授权许可,所以不需要其他授权请求。

    4.4.2. 访问令牌请求

    客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起请求:
  • grant_type
    必需的。值必须设置为“client_credentials”。
  • scope
    可选的。如3.3节所述的访问请求的范围。

客户端必须如3.2.1所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials

授权服务器必须对客户端进行身份验证。

4.4.3. 访问令牌响应

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。刷新令牌不应该包含在内。 如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

一个样例成功响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600, "example_parameter":"example_value"
}

4.5. 扩展许可

通过使用绝对URI作为令牌端点的“grant_type”参数的值指定许可类型,并通过添加任何其他需要的参数,客户端使用扩展许可类型。

例如,采用[OAuth-SAML]定义的安全断言标记语言(SAML)2.0断言许可类型请求访问令牌,客户端可以使用TLS发起如下的HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2bearer&assertion=PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU[...为简洁起见省略...]aG5TdGF0ZW1lbnQ-PC9Bc3NlcnRpb24-

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

5. 颁发访问令牌

如果访问令牌请求是有效的且被授权,授权服务器如5.1节所述颁发访问令牌以及可选的刷新令牌。如果请求因客户端身份验证失败或无效,授权服务器如5.2节所述的返回错误响应。

  • 5.1.成功响应
  • 5.2.错误响应

    5.1. 成功的响应

    授权服务器颁发访问令牌和可选的刷新令牌,通过向HTTP响应实体正文中添加下列参数并使用200(OK)状态码构造响应:
  • access_token
    必需的。授权服务器颁发的访问令牌。
  • token_type
    必需的。如7.1节所述的颁发的令牌的类型。值是大小写敏感的。
  • expires_in
    推荐的。以秒为单位的访问令牌生命周期。例如,值“3600”表示访问令牌将在从生成响应时的1小时后到期。如果省略,则授权服务器应该通过其他方式提供过期时间,或者记录默认值。
  • refresh_token
    可选的。刷新令牌,可以用于如第6节所述使用相同的授权许可获得新的访问令牌。
  • scope
    可选的,若与客户端请求的范围相同;否则,必需的。如3.3节所述的访问令牌的范围。

这些参数使用RFC4627定义的“application/json”媒体类型包含在HTTP响应实体正文中。通过将每个参数添加到最高结构级别, 参数被序列化为JavaScript对象表示法(JSON)的结构。参数名称和字符串值作为JSON字符串类型包含。数值的值作为JSON数字类型包含。参数顺序无关并可以变化。

在任何包含令牌、凭据或其他敏感信息的响应中,授权服务器必须在其中包含值为“no-store”的HTTP“Cache-Control”响应头部域RFC2616,和值为“no-cache”的“Pragma”响应头部域RFC2616。例如:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

客户端必须忽略响应中不能识别的值的名称。令牌和从授权服务器接收到的值的大小未定义。客户端应该避免对值的大小做假设。授权服务器应记录其发放的任何值的大小。

5.2. 错误响应

授权服务器使用HTTP 400(错误请求)状态码响应,在响应中包含下列参数:

  • error
    必需的。下列ASCII[USASCII]错误代码之一:

    • invalid_request
      请求缺少必需的参数、包含不支持的参数值(除了许可类型)、重复参数、包含多个凭据、采用超过一种客户端身份验证机制或其他不规范的格式。
    • invalid_client
      客户端身份验证失败(例如,未知的客户端,不包含客户端身份验证,或不支持的身份验证方法)。授权服务器可以返回HTTP 401(未授权)状态码来指出支持的HTTP身份验证方案。如果客户端试图通过“Authorization”请求标头域进行身份验证,授权服务器必须响应HTTP 401(未授权)状态码,并包含与客户端使用的身份验证方案匹配的“WWW-Authenticate”响应标头字段。
    • invalid_grant
      提供的授权许可(如授权码、资源所有者凭据)或刷新令牌无效、过期、吊销、与在授权请求使用的重定向URI不匹配或颁发给另一个客户端。
    • unauthorized_client
      进行身份验证的客户端没有被授权使用这种授权许可类型。
    • unsupported_grant_type
      授权许可类型不被授权服务器支持。
    • invalid_scope
      请求的范围无效、未知的、格式不正确或超出资源所有者许可的范围。

    “error”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • error_description
    可选的。提供额外信息的人类可读的ASCII[USASCII]文本,用于协助客户端开发人员理解所发生的错误。“error_description”参数的值不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。
  • error_uri
    可选的。指向带有有关错误的信息的人类可读网页的URI,用于提供客户端开发人员关于该错误的额外信息。“error_uri”参数值必须符合URI参考语法,因此不能包含集合%x21/%x23-5B /%x5D-7E以外的字符。

这些参数使用RFC4627定义的“application/json”媒体类型包含在HTTP响应实体正文中。通过将每个参数添加到最高结构级别, 参数被序列化为JavaScript对象表示法(JSON)的结构。参数名称和字符串值作为JSON字符串类型包含。数值的值作为JSON数字类型包含。参数顺序无关并可以变化。例如:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "error":"invalid_request"
}

6. 刷新访问令牌

若授权服务器给客户端颁发了刷新令牌,客户端通过使用按附录B“application/x-www-form-urlencoded”格式在HTTP请求实体正文中发送下列UTF-8字符编码的参数向令牌端点发起刷新请求:

  • grant_type
    必需的。值必须设置为“refresh_token”。
  • refresh_token
    必需的。颁发给客户端的刷新令牌。
  • scope
    可选的。如3.3节所述的访问请求的范围。请求的范围不能包含任何不是由资源所有者原始许可的范围,若省略,被视为与资源所有者原始许可的范围相同。

因为刷新令牌通常是用于请求额外的访问令牌的持久凭证,刷新令牌绑定到被它被颁发给的客户端。如果客户端类型是机密的或客户端被颁发了客户端凭据(或选定的其他身份验证要求),客户端必须如3.2.1节所述与授权服务器进行身份验证。

例如,客户端使用传输层安全发起如下HTTP请求(额外的换行仅用于显示目的):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

授权服务器必须:

  • 要求机密客户端或任何被颁发了客户端凭据(或有其他身份验证要求)的客户端进行客户端身份验证,
  • 若包括了客户端身份验证,验证客户端身份并确保刷新令牌是被颁发给进行身份验证的客户端的,并
  • 验证刷新令牌。

如果有效且被授权,授权服务器如5.1节所述颁发访问令牌。如果请求因验证失败或无效,授权服务器5.2节所述返回错误响应。

授权服务器可以颁发新的刷新令牌,在这种情况下,客户端必须放弃旧的刷新令牌,替换为新的刷新令牌。在向客户端颁发新的刷新令牌后授权服务器可以撤销旧的刷新令牌。若颁发了新的刷新令牌,刷新令牌的范围必须与客户端包含在请求中的刷新令牌的范围相同。

7. 访问受保护资源

通过向资源服务器出示访问令牌,客户端访问受保护资源。资源服务器必须验证访问令牌,并确保它没有过期且其范围涵盖了请求的资源。资源服务器用于验证访问令牌的方法(以及任何错误响应)超出了本规范的范围,但一般包括资源服务器和授权服务器之间的互动或协调。

客户端使用访问令牌与资源服务器进行证认的方法依赖于授权服务器颁发的访问令牌的类型。通常,它涉及到使用具有所采用的访问令牌类型的规范定义的身份验证方案(如RFC6750)的HTTP“Authorization”的请求标头字段RFC2617

7.1. 访问令牌类型

访问令牌的类型给客户端提供了成功使用该访问令牌(和类型指定的属性)发起受保护资源请求所需的信息 若客户端不理解令牌类型,则不能使用该访问令牌。

例如,RFC6750定义的“bearer”令牌类型简单的在请求中包含访问令牌字符串来使用:

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer F_9.B5f-4.1JqM

而[OAuth-HTTP-MAC]定义的“mac”令牌类型通过与许可类型一起颁发用于对HTTP请求中某些部分签名的消息认证码(MAC)的密钥来使用。

GET /resource/1 HTTP/1.1
Host: example.com
Authorization: MAC id="h480djs93hd8",nonce="274312:dj83hs9s",mac="kDZvddkndxvhGRXZhvuDjEWhGeE="

提供上面的例子仅作说明用途。建议开发人员在使用前查阅RFC6750和[OAuth-HTTP-MAC]规范。

每一种访问令牌类型的定义指定与“access_token”响应参数一起发送到客户端的额外属性。它还定义了HTTP验证方法当请求受保护资源时用于包含访问令牌。

7.2. 错误响应

如果资源访问请求失败,资源服务器应该通知客户端该错误。虽然规定这些错误响应超出了本规范的范围,但是本文档在11.4节建立了一张公共注册表,用作OAuth令牌身份验证方案之间分享的错误值。

主要为OAuth令牌身份验证设计的新身份验证方案应该定义向客户端提供错误状态码的机制,其中允许的错误值限于本规范建立的错误注册表中。

这些方案可以限制有效的错误代码是注册值的子集。如果错误代码使用命名参数返回,该参数名称应该是“error”。

其他能够被用于OAuth令牌身份验证的方案,但不是主要为此目的而设计的,可以帮顶他们的错误值到相同方式的注册表项。

新的认证方案也可以选择指定使用“error_description”和"error_uri"参数,用于以本文档中用法相同的方式的返回错误信息。

8. 可扩展性

采用URI命名的类型应该限定于特定供应商的实现,它们不是普遍适用的并且特定于使用它们的资源服务器的实现细节。

所有其他类型都必须注册。类型名称必需符合type-name ANBF。如果类型定义包含了一种新的HTTP身份验证方案,该类型名称应该与该HTTP身份验证方案名称一致(如RFC2617定义)。令牌类型“example”被保留用于样例中。

type-name  = 1*name-char
name-char  = "-" / "." / "_" / DIGIT / ALPHA

8.2. 定义新的端点参数

用于授权端点或令牌端点的新的请求或响应参数按照11.2节中的过程在OAuth参数注册表中定义和注册。

参数名称必须符合param-name ABNF,并且参数值的语法必须是明确定义的(例如,使用ABNF,或现有参数的语法的引用)。

param-name  = 1*name-char
name-char   = "-" / "." / "_" / DIGIT / ALPHA

不是普遍适用的并且特定于使用它们的授权服务器的实现细节的未注册的特定供应商的参数扩展应该采用特定供应商的前缀(例如,以“companyname_”开头),从而不会与其他已注册的值冲突。

8.3. 定义新的授权许可类型

新的授权许可类型可以通过赋予它们一个“grant_type”参数使用的唯一的绝对URI来定义。如果扩展许可类型需要其他令牌端点参数,它们必须如11.2节所述在OAuth参数注册表中注册。

8.4. 定义新的授权端点响应类型

用于授权端点的新的响应类型按照11.3节中的过程在授权端点响应类型注册表中定义和注册。响应类型名称必须符合response-type ABNF。

response-type  = response-name *( SP response-name )
response-name  = 1*response-char
response-char  = "_" / DIGIT / ALPHA

如果响应类型包含一个或多个空格字符(%x20),它被看作是一个空格分隔的值列表,其中的值的顺序不重要。只有一种值的顺序可以被注册,它涵盖了相同的值的集合的所有其他排列。

例如,响应类型“token code”未由本规范定义。然而,一个扩展可以定义和注册“token code”响应类型。 一旦注册,相同的组合“code token”不能被注册,但是这两个值都可以用于表示相同的响应类型。

8.5. 定义其他错误代码

在协议扩展(例如,访问令牌类型、扩展参数或扩展许可类型等)需要其他错误代码用于授权码许可错误响应(4.1.2.1节)、隐式许可错误响应(4.2.2.1节)、令牌错误响应(5.2节)或资源访问错误响应(7.2节)的情况下,这些错误代码可以被定义。

如果用于与它们配合的扩展是已注册的访问令牌类型,已注册的端点参数或者扩展许可类型,扩展错误代码必须被注册。用于未注册扩展的错误代码可以被注册。

错误代码必须符合的error ABNF,且可能的话应该以一致的名称作前缀。例如,一个表示给扩展参数“example”设置了无效值的错误应该被命名为“example_invalid”。

 error      = 1*error-char
 error-char = %x20-21 / %x23-5B / %x5D-7E

9. 本机应用程序

本机应用程序是安装和执行在资源所有者使用的设备上的客户端(例如,桌面程序,本机移动应用)。本机应用程序需要关于安全、平台能力和整体最终用户体验的特别注意事项。

授权端点需要在客户端和资源所有者用户代理之间进行交互。本机应用程序可以调用外部的用户代理,或在应用程序中嵌入用户代理。例如:

  • 外部用户代理-本机应用程序可以捕获来自授权服务器的响应。它可以使用带有操作系统已注册方案的重定向URI调用客户端作为处理程序,手动复制粘贴凭据,运行本地Web服务器,安装用户代理扩展,或者通过提供重定向URI来指定客户端控制下的服务器托管资源,反过来使响应对本机应用程序可用。
  • 嵌入式用户代理-通过监视资源加载过程中发生的状态变化或者访问用户代理的cookies存储,本机应用程序直接与嵌入式用户代理通信,获得响应。
    当在外部或嵌入式用户代理中选择时,开发者应该考虑如下:
  • 外部用户代理可能会提高完成率,因为资源所有者可能已经有了与授权服务器的活动会话,避免了重新进行身份验证的需要。它提供了熟悉的最终用户体验和功能。资源所有者可能也依赖于用户代理特性或扩展帮助他进行身份验证(例如密码管理器、两步设备读取器)
  • 嵌入式用户代理可能会提供更好的可用性,因为它避免了切换上下文和打开新窗口的需要。
  • 嵌入式用户代理构成了安全挑战,因为资源所有者在一个未识别的窗口中进行身份验证,无法获得在大多数外部用户代理中的可视的保护。嵌入式用户代理教育用户信任未标识身份验证请求(使钓鱼攻击更易于实施)。
    当在隐式许可类型和授权码许可类型中选择时,下列应该被考虑:
  • 使用授权码许可类型的本机应用程序应该这么做而不需使用用户凭据,因为本机应用程序无力保持客户端凭据的机密性。
  • 当使用隐式许可类型流程时,刷新令牌不会返回,这就要求一旦访问令牌过期就要重复授权过程。

10. 安全考量

作为一个灵活的可扩展的框架,OAuth的安全性考量依赖于许多因素。 以下小节提为实现者提供了聚焦在2.1节所述的三种客户端配置上的安全指南:Web应用、基于用户代理的应用和本地应用程序。

全面的OAuth安全模型和分析以及该协议设计的背景在[OAuth-THREATMODE]中提供。

授权不得向本地应用程序或基于用户代理的应用客户端颁发客户端密码或其他客户端凭据用于客户端验证目的。授权服务器可以颁发客户端密码或其他凭据给专门的设备上特定安装的本地应用程序客户端。

当客户端身份验证不可用时,授权服务器应该采用其他方式来验证客户端的身份-例如,通过要求客户端重定向URI的注册或者引入资源所有者来确认身份。当请求资源所有者授权时,有效的重定向URI是不足以验证客户端的身份,但可以用来防止在获得资源所有者授权后将凭据传递给假冒的客户端。

授权服务器必须考虑与未进行身份验证的客户端交互的安全实现并采取措施限制颁发给这些客户端的其他凭据(如刷新令牌)的潜在泄露。

10.2. 客户端仿冒

如果被仿冒的客户端不能,或无法保持其客户端凭据保密。恶意客户端可能冒充其他客户端,并获得对受保护资源的访问权限。

授权服务器任何可能的时候必须验证客户端身份。如果授权服务器由于客户端的性质无法对客户端进行身份验证,授权服务器必须要求注册任何用于接收授权响应的重定向URI并且应该利用其他手段保护资源所有者防止这样的潜在仿冒客户端。例如,授权服务器可以引入资源所有者来帮助识别客户端和它的来源。

授权服务器应该实施显式的资源所有者身份验证并且提供给资源所有者有关客户端及其请求的授权范围和生命周期的信息。由资源所有者在当前客户端上下文中审查信息并授权或拒绝该请求。

授权服务器未对客户端进行身份验证(没有活动的资源所有者交互)或未依靠其他手段确保重复的请求来自于原始客户端而非冒充者时,不应该自动处理重复的授权请求。

10.3. 访问令牌

访问令牌凭据(以及任何机密的访问令牌属性)在传输和储存时必须保持机密性,并只与授权服务器、访问令牌生效的资源服务器和访问令牌被颁发的客户端共享。访问令牌凭据必须只能使用带有RFC2818定义的服务器身份验证的1.6节所述的TLS 传输。

当使用隐式授权许可类型时,访问令牌在URI片段中传输,这可能泄露访问令牌给未授权的一方。

授权服务器必须确保访问令牌不能被生成、修改或被未授权一方猜测而产生有效的访问令牌。

客户端应该为最小范围的需要请求访问令牌。授权服务器在选择如何兑现请求的范围时应该将客户端身份考虑在内,且可以颁发具有比请求的更少的权限的访问令牌。

本规范未给资源服务器提供任何方法来确保特定的客户端提交给它的访问令牌是授权服务器颁发给此客户端的。

10.4. 刷新令牌

授权服务器可以给Web应用客户端和本机应用程序客户端颁发刷新令牌。

刷新令牌在传输和储存时必须保持机密性,并只与授权服务器和刷新令牌被颁发的客户端共享。授权服务器必须维护刷新令牌和它被颁发给的客户端之间的绑定。刷新令牌必须只能使用带有RFC2818定义的服务器身份验证的1.6所述的TLS 传输。
授权服务器必须验证刷新令牌和客户端身份之间的绑定,无论客户端身份是否能被验证。当无法进行客户端身份验证时,授权服务器应该采取其他手段检测刷新令牌滥用。

例如,授权服务器可以使用刷新令牌轮转机制,随着每次访问令牌刷新响应,新的刷新令牌被颁发。以前的刷新令牌被作废但是由授权服务器保留。如果刷新令牌被泄露,随后同时被攻击者和合法客户端使用,他们中一人将提交被作废的刷新令牌,这将通知入侵给授权服务器。

授权服务器必须确保刷新令牌不能被生成、修改或被未授权一方猜测而产生有效的刷新令牌。

10.5. 授权码

授权码的传输应该建立在安全通道上,客户端应该要求在它的重定向URI上使用TLS,若该URI指示了一个网络资源。 由于授权码由用户代理重定向传输,它们可能潜在地通过用户代理历史记录和HTTP参照标头被泄露。

授权码明以纯文本承载凭据使用,用于验证在授权服务器许可权限的资源所有者就是返回到客户端完成此过程的相同的资源所有者。因此,如果客户端依赖于授权码作为它自己的资源所有者身份验证,客户端重定向端点必须要求使用TLS。

授权码必须是短暂的且是单用户的。如果授权服务器观察到多次以授权码交换访问令牌的尝试,授权服务器应该试图吊销所有基于泄露的授权码而颁发的访问令牌。

如果客户端可以进行身份验证,授权服务器必须验证客户端身份,并确保授权码颁发给了同一个客户端。

10.6. 授权码重定向URI伪造

当使用授权码许可类型请求授权时,客户端可以通过“redirect_uri”参数指定重定向URI。 如果攻击者能够伪造重定向URI的值,这可能导致授权服务器向攻击者控制的URI重定向带有授权码的资源所有者用户代理。

攻击者可以在合法客户端上创建一个帐户,并开始授权流程。当攻击者的用户代理被发送到授权服务器来许可访问权限时,攻击者抓取合法客户端提供的授权URI并用攻击者控制下的URI替换客户端的重定向URI。 攻击者然后欺骗受害者顺着仿冒的链接来对合法客户端授权访问权限。

一旦在授权服务器——受害者被唆使代表一个合法的被信任的客户端使用正常有效的请求——授权该请求时。受害者然后带着授权码重定向到受攻击者控制的端点。通过使用客户端提交的原始重定向URI向客户端发送授权码,攻击者完成授权流程。客户端用授权码交换访问令牌并与将它与攻击者的客户端账号关联,该账户现在能获得受害者授权的(通过客户端)对访问受保护资源的访问权限。

为了防止这种攻击,授权服务器必须确保用于获得授权码的重定向URI与当用授权码交换访问令牌时提供的重定向URI相同。授权服务器必须要求公共客户端,并且应该要求机密客户注册它们的重定向URI。如果在请求中提供一个重定向URI,授权服务器必须验证对注册的值。如果在请求中提供了重定向URI,授权服务器必须对比已注册的。
10.7. 资源所有者密码凭据

资源所有者密码凭据许可类型通常用于遗留或迁移原因。它降低了由客户端存储用户名和密码的整体风险,但并没有消除泄露高度特权的凭证给客户端的需求。

这种许可类型比其他许可类型承载了更高的风险,因为它保留了本协议寻求避免的密码反模式。客户端可能滥用密码或密码可能会无意中被泄露给攻击者(例如,通过客户端保存的日志文件或其他记录)。

此外,由于资源拥有者对授权过程没有控制权(在转手它的凭据给客户端后资源所有者的参与结束),客户端可以获得比资源所有者预期的具有更大范围的访问令牌。授权服务器应该考虑由这种许可类型颁发的访问令牌的范围和寿命。

授权服务器和客户端应该尽量减少这种许可类型的使用,并尽可能采用其他许可类型。

10.8. 请求机密性

访问令牌、刷新令牌、资源所有者密码和客户端凭据不能以明文传输。授权码不应该以明文传输。

“state”和“scope”参数不应该包含敏感的客户端或资源所有者的纯文本信息,因为它们可能在不安全的通道上被传输或被不安全地存储。

10.9. 确保端点真实性

为了防止中间人攻击,授权服务器必须对任何被发送到授权和令牌端点的请求要求RFC2818中定义的具有服务器身份验证的TLS 的使用。客户端必须按RFC6125定义且按照它服务器身份进行身份验证的需求验证授权服务器的的TLS证书。

10.10. 凭据猜测攻击

授权服务器必须防止攻击者猜测访问令牌、授权码、刷新令牌、资源所有者密码和客户端凭据。

攻击者猜测已生成令牌(和其它不打算被最终用户掌握的凭据)的概率必须小于或等于2 ^(-128),并且应该小于或等于2 ^(-160)。

授权服务器必须采用其他手段来保护打算给最终用户使用的凭据。

10.11. 钓鱼攻击

本协议或类似协议的广泛部署,可能导致最终用户变成习惯于被重定向到要求输入他们的密码的网站的做法。

如果最终用户在输入他们的凭据前不注意辨别这些网站的真伪,这将使攻击者利用这种做法窃取资源所有者的密码成为可能。

服务提供者应尝试教育最终用户有关钓鱼攻击构成的风险,并且应该为最终用户提供使确认它们的站点的真伪变得简单的机制。客户端开发者应该考虑他们如何与用户代理(例如,外部的和嵌入式的)交互的安全启示以及最终用户辨别授权服务器真伪的能力。

为了减小钓鱼攻击的风险,授权服务器必须要求在用于最终用户交互的每个端点上使用TLS。

10.12. 跨站请求伪造

跨站请求伪造(CSRF)是一种漏洞利用,攻击者致使受害的最终用户按恶意URI(例如以误导的链接、图片或重定向提供给用户代理)到达受信任的服务器(通常由存在有效的会话Cookie而建立)。

针对客户端的重定向URI的CSRF攻击允许攻击者注入自己的授权码或访问令牌,这将导致在客户端中使用与攻击者的受保护资源关联的访问令牌而非受害者的(例如,保存受害者的银行账户信息到攻击者控制的受保护资源)。

客户端必须为它的重定向URI实现CSRF保护。这通常通过要求向重定向URI端点发送的任何请求包含该请求对用户代理身份认证状态的绑定值(例如,用于对用户代理进行身份验证的会话Cookie的哈希值)来实现。客户端应该使用“state”请求参数在发起授权请求时向授权服务器传送该值。

一旦从最终用户获得授权,授权服务器重定向最终用户的用户代理带着要求的包含在“state”参数中的绑定值回到客户端。 通过该绑定值与用户代理的身份验证状态的匹配,绑定值使客户端能够验证请求的有效性。用于CSRF保护的绑定值必须包含不可猜测的值(如10.10节所述)且用户代理的身份验证状态(例如会话Cookie、HTML5本地存储)必须保存在只能被客户端和用户代理访问的地方(即通过同源策略保护)。

针对授权服务器的授权端点的CSRF攻击可能导致攻击者获得最终用户为恶意客户端的授权而不牵涉或警告最终用户。

授权服务器必须为它的授权端点实现CSRF保护并且确保在资源所有者未意识到且无显式同意时恶意客户端不能获得授权。

10.13. 点击劫持

在点击劫持攻击中,攻击者注册一个合法客户端然后构造一个恶意站点,在一个透明的覆盖在一组虚假按钮上面的嵌入框架中加载授权服务器的授权端点Web页面,这些按钮被精心构造恰好放置在授权页面上的重要按钮下方。当最终用户点击了一个误导的可见的按钮时,最终用户实际上点击了授权页面上一个不可见的按钮(例如“授权”按钮)。 这允许攻击者欺骗资源所有者许可它的客户端最终用户不知晓的访问权限。

为了防止这种形式的攻击,在请求最终用户授权时本机应用程序应该使用外部浏览器而非应用程序中嵌入的浏览器。 对于大多数较新的浏览器,避免嵌入框架可以由授权服务器使用(非标准的)“x-frame-options”标头实施。 该标头可以有两个值,“deny”和“sameorigin”,它将阻止任何框架,或按不同来源的站点分别构造框架。 对于较旧的浏览器,JavaScript框架破坏技术可以使用,但可能不会在所有的浏览器中生效。

10.14. 代码注入和输入验证

代码注入攻击当程序使用的输入或其他外部变量未清洗而导致对程序逻辑的修改时发生。 这可能允许攻击者对应用程序的设备或它的数据的访问权限,导致服务拒绝或引入许多的恶意副作用。

授权服务器和客户端必须清洗(并在可能的情况下验证)收到的任何值--特别是,“state”和“redirect_uri”参数的值。

10.15. 自由重定向器

授权服务器、授权端点和客户端重定向端点可能被不当配置,被作为自由重定向器。自由重定向器是一个使用参数自动地向参数值指定而无任何验证的地址重定向用户代理的端点。

自由重定向器可被用于钓鱼攻击,或者被攻击者通过使用熟悉的受信任的目标地址的URI授权部分使最终用户访问恶意站点。此外,如果授权服务器允许客户端只注册部分的重定向URI,攻击者可以使用客户端操作的自由重定向器构造重定向URI,这将跳过授权服务器验证但是发送授权码或访问令牌给攻击者控制下的端点。

10.16. 在隐式流程中滥用访问令牌假冒资源所有者

对于使用隐式流程的公共客户端,本规范没有为客户端提供任何方法来决定访问令牌颁发给的是什么样的客户端。

资源所有者可能通过给攻击者的恶意客户端许可访问令牌自愿委托资源的访问权限。这可能是由于钓鱼或一些其他借口。攻击者也可能通过其他机制窃取令牌。 攻击者然后可能会尝试通过向合法公开客户端提供该访问令牌假冒资源拥有者。

在隐式流程(response_type=token)中,攻击者可以轻易转换来自授权服务器的响应中的令牌,用事先颁发给攻击者的令牌替换真实的访问令牌。

依赖于在返回通道中传递访问令牌识别客户端用户的与本机应用程序通信的服务器可能由攻击者创建能够注入随意的窃取的访问令牌的危险的程序被类似地危及。

任何做出只有资源所有者能够提交给它有效的为资源的访问令牌的假设的公共客户端都是易受这种类型的攻击的。

这种类型的攻击可能在合法的客户端上泄露有关资源所有者的信息给攻击者(恶意客户端)。这也将允许攻击者在合法客户端上用和资源所有者相同的权限执行操作,该资源所有者最初许可了访问令牌或授权码。

客户端对资源拥有者进行身份验证超出了本规范的范围。任何使用授权过程作为客户端对受委托的最终用户进行身份验证的形式的规范(例如,第三方登录服务)不能在没有其他的客户端能够判断访问令牌是否颁发是颁发给它使用的安全机制的情况下使用隐式流程(例如,限制访问令牌的受众)。

11. IANA考量

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册访问令牌类型。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“访问令牌类型example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.1.1. 注册模板

  • Type name:

    请求的名称(例如,“example”)。

  • Additional Token Endpoint Response Parameters:

    随“access_token”参数一起返回的其他响应参数。 新的参数都必须如11.2节所述在OAuth参数注册表中分别注册。

  • HTTP Authentication Scheme(s):

    HTTP身份验证方案名称,如果有的话,用于使用这种类型的访问令牌对受保护资源进行身份验证。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。 对于其他,给出负责的部分的名称。 其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。 相关章节的指示也可以包含但不是必需的。

    11.2. OAuth参数注册表

    本规范建立OAuth参数注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册列入授权端点请求、授权端点响应、令牌端点请求或令牌端点响应的其他参数。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如,参数“example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.2.1. 注册模板

  • Parameter name:

    请求的名称(例如,“example”)。

  • Parameter usage location:

    参数可以使用的位置。 可能的位置为授权请求、授权响应、令牌请求或令牌响应。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的。

    11.2.2. 最初的注册表内容

    OAuth参数注册表中的初始内容:

  • Parameter name: client_id
  • Parameter usage location: authorization request, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: client_secret
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: response_type
  • Parameter usage location: authorization request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: redirect_uri
  • Parameter usage location: authorization request, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: scope
  • Parameter usage location: authorization request, authorization response, token request, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: state
  • Parameter usage location: authorization request, authorization response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: code
  • Parameter usage location: authorization response, token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: error_description
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: error_uri
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: grant_type
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: access_token
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: token_type
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: expires_in
  • Parameter usage location: authorization response, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: username
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: password
  • Parameter usage location: token request
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Parameter name: refresh_token
  • Parameter usage location: token request, token response
  • Change controller: IETF
  • Specification document(s):RFC 6749

11.3. OAuth授权端点响应类型注册表

本规范建立OAuth授权端点响应类型注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册授权端点使用的其他响应类型。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“响应类型example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.3.1. 注册模板

  • Response type name:

    请求的名称(例如,“example”)。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的

    11.3.2. 最初的注册表内容

    OAuth授权端点响应类型注册表的初始内容:

  • Response type name: code
  • Change controller: IETF
  • Specification document(s):RFC 6749
  • Response type name: token
  • Change controller: IETF
  • Specification document(s):RFC 6749

11.4. OAuth扩展错误注册表

本规范建立OAuth扩展错误注册表。

在oauth-ext-review@ietf.org邮件列表上的两周的审查期后,根据一位或多位指定的专家的建议下,按规范需求(RFC5226)注册与其他协议扩展(例如,扩展的许可类型、访问令牌类型或者扩展参数)一起使用的其他错误代码。然而,为允许发表之前的值的分配,指定的专家(们)一旦他们对这样的规范即将发布感到满意可以同意注册。

注册请求必须使用正确的主题(例如“错误代码example”的请求)发送到oauth-ext-review@ietf.org邮件列表来审查和评论。

在审查期间,指定的专家(们)将同意或拒绝该注册请求,向审查列表和IANA通报该决定。拒绝应该包含解释,并且可能的话,包含如何使请求成功的建议。

IANA必须只接受来自指定的专家(们)的注册表更新并且应该引导所有注册请求至审查邮件列表。

11.4.1. 注册模板

  • Error name:

    请求的名称(例如,“example”)。错误名称的值
    不能包含集合%x20-21 /%x23-5B /%x5D-7E以外的字符。

  • Error usage location:

    错误使用的位置。可能的位置是授权代码许可错误响应(4.1.2.1节),隐式许可错误响应(4.2.2.1节),令牌错误响应(5.2节),或资源访问错误的响应(7.2节)。

  • Related protocol extension:

    与错语代码一起使用的扩展许可类型、访问令牌类型或扩展参数的名称。

  • Change controller:

    对于标准化过程的RFC,指定为“IETF”。对于其他,给出负责的部分的名称。其他细节(例如,邮政地址,电子邮件地址,主页URI)也可以包括在内。

  • Specification document(s):

    指定参数的文档的引用文献,最好包括可以用于检索文档副本的URI。相关章节的指示也可以包含但不是必需的。

12. 参考文献

  • 12.1.规范性文献
  • 12.2.参考性文献

    12.1. 规范性参考文件

  • RFC2119
    Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14,RFC 2119, March 1997.
  • RFC2246
    Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", RFC 2246, January 1999.
  • RFC2616
    Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
  • RFC2617
    Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999.
  • RFC2818
    Rescorla, E., "HTTP Over TLS", RFC 2818, May 2000.
  • [RFC3629]
    Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.
  • RFC3986
    Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, January 2005.
  • RFC4627
    Crockford, D., "The application/json Media Type for JavaScript Object Notation (JSON)", RFC 4627, July 2006.
  • RFC4949
    Shirey, R., "Internet Security Glossary, Version 2", RFC 4949, August 2007.
  • RFC5226
    Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26,RFC 5226, May 2008.
  • RFC5234
    Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", STD 68, RFC 5234, January 2008.
  • RFC5246
    Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, August 2008.
  • RFC6125
    Saint-Andre, P. and J. Hodges, "Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS)", RFC 6125, March 2011.
  • [USASCII]
    American National Standards Institute, "Coded Character Set -- 7-bit American Standard Code for Information Interchange", ANSI X3.4, 1986.
  • [W3C.REC-html401-19991224]
    Raggett, D., Le Hors, A., and I. Jacobs, "HTML 4.01 Specification", World Wide Web Consortium Recommendation REC-html401-19991224, December 1999,http://www.w3.org/TR/1999/REC-html401-19991224.
  • [W3C.REC-xml-20081126]
    Bray, T., Paoli, J., Sperberg-McQueen, C., Maler, E., and F. Yergeau, "Extensible Markup Language (XML) 1.0 (Fifth Edition)", World Wide Web Consortium Recommendation REC-xml-20081126, November 2008,http://www.w3.org/TR/2008/REC-xml-20081126.

    12.2. 参考性引用文献

  • [OAuth-HTTP-MAC]
    Hammer-Lahav, E., Ed., "HTTP Authentication: MAC Access Authentication", Work in Progress, February 2012.
  • [OAuth-SAML2]
    Campbell, B. and C. Mortimore, "SAML 2.0 Bearer Assertion Profiles for OAuth 2.0", Work in Progress, September 2012.
  • [OAuth-THREATMODEL]
    Lodderstedt, T., Ed., McGloin, M., and P. Hunt, "OAuth 2.0 Threat Model and Security Considerations", Work in Progress, October 2012.
  • [OAuth-WRAP]
    Hardt, D., Ed., Tom, A., Eaton, B., and Y. Goland, "OAuth Web Resource Authorization Profiles", Work in Progress, January 2010.
  • RFC5849
    Hammer-Lahav, E., "The OAuth 1.0 Protocol", RFC 5849, April 2010.
  • RFC6750
    Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, October 2012.
]]>
0
<![CDATA[一个奇怪的VBA函数:在excel里计算对角线的和]]> http://www.udpwork.com/item/16242.html http://www.udpwork.com/item/16242.html#reviews Fri, 28 Apr 2017 00:59:12 +0800 Felix021 http://www.udpwork.com/item/16242.html 想要计算一个区域里对角线的和,但SUMIF里面的那个criteria实在太简陋了,只能用vba来实现,大概长这样:

引用
Function sum_diag(n As Integer, ParamArray args() As Variant) As Variant
    result = 0

    For i = LBound(args) To UBound(args)
        For Each elem In args(i)
            If elem.Row + elem.Column = n Then
                result = result + elem.Value
            End If
        Next elem
    Next i
    sum_diag = result
End Function


然后这么用:
引用
=sum_diag(ROW()+COLUMN(), $B$1:$D$3)


点击在新窗口中浏览此图片 ]]>
想要计算一个区域里对角线的和,但SUMIF里面的那个criteria实在太简陋了,只能用vba来实现,大概长这样:

引用
Function sum_diag(n As Integer, ParamArray args() As Variant) As Variant
    result = 0

    For i = LBound(args) To UBound(args)
        For Each elem In args(i)
            If elem.Row + elem.Column = n Then
                result = result + elem.Value
            End If
        Next elem
    Next i
    sum_diag = result
End Function


然后这么用:
引用
=sum_diag(ROW()+COLUMN(), $B$1:$D$3)


点击在新窗口中浏览此图片 ]]>
0
<![CDATA[[转]Spring 5 新功能:函数式 Web 框架]]> http://www.udpwork.com/item/16241.html http://www.udpwork.com/item/16241.html#reviews Wed, 26 Apr 2017 17:51:33 +0800 鸟窝 http://www.udpwork.com/item/16241.html 英文原文:New in Spring 5: Functional Web Frameworkby
中文翻译:Spring 5 新功能:函数式 Web 框架by 开源中国

就像在昨天Juergen发布的博客的一样,Spring 5.0框架第二个里程碑版本中介绍了一个新的函数式web框架。在这篇文章中,我将更详细的介绍这个框架。

紧记该函数式web框架是在Spring5.0第一个里程碑版本基础上构建的。并且我们依旧提供基于注解的请求处理(例如@Controller,@RequestMapping),关于基于注解的请求处理部分的相关信息请查阅关于Spring5.0第一个里程碑版本的博客。

示例

我们选用示例程序作为开始。下面是一个响应资源库用于暴露Person对象。这个响应资源库与传统的无响应资源库类似,除了Flux对应传统的 List,Mono对应传统的 Person对象。Mono作为完成标识:用于指示保存工作完成.更多Reactor 类型信息请查阅Dave发布的博客

1234
public interface PersonRepository {  Mono<Person> getPerson(int id);  Flux<Person> allPeople();  Mono<Void> savePerson(Mono<Person> person);}

这里我们介绍如何使用新的函数式web框架暴露资源库:

1234567891011121314151617
RouterFunction<?> route = route(GET("/person/{id}"),  request -> {    Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id"))      .map(Integer::valueOf)      .then(repository::getPerson);    return Response.ok().body(fromPublisher(person, Person.class));  })  .and(route(GET("/person"),    request -> {      Flux<Person> people = repository.allPeople();      return Response.ok().body(fromPublisher(people, Person.class));    }))  .and(route(POST("/person"),    request -> {      Mono<Person> person = request.body(toMono(Person.class));      return Response.ok().build(repository.savePerson(person));    }));

这里我们介绍如何运行它,下面是Reactor Netty的示例:

12345
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);ReactorHttpHandlerAdapter adapter =  new ReactorHttpHandlerAdapter(httpHandler);HttpServer server = HttpServer.create("localhost", 8080);server.startAndAwait(adapter);

最后要做的是,进行一次尝试请求:

12
$ curl 'http://localhost:8080/person/1'{"name":"John Doe","age":42}

上面的介绍覆盖了很多内容,下面让我们深入挖掘下!

核心组件

我将通过依次介绍HandlerFunction,RouterFunction以及FilterFunction等核心组件来介绍整个框架。这三个接口以及本文中其他类型都可以在org.springframework.web.reactive.function包中找到。

HandlerFunction

新框架的起点是HandlerFunction,其实质是Function>,其中的Request 和 Response都是新定义的不可变接口,提供了基础的对JDK8优化的HTTP消息描述DSL。有一个便捷的构造Response实例的构造器,与ResponseEntity中的十分相似。注解方式中与HandlerFunction相对应的是@RequestMapping所注解的方法。,>

如下是“Hello World”的处理方法,它返回了状态为200,body为字符串的消息。

12
HandlerFunction<String> helloWorld =  request -> Response.ok().body(fromObject("Hello World"));

如上,构建于Reactor之上的处理方法是完全的响应式的(reactive),它们可以接受Flux、Mono或者其他相应流(Reactive Streams)的发布者作为返回类型的参数。

需要注意的是处理方法本身是没有副作用的,因为它将response作为返回值,而不是作为参数(对比Servlet.service(ServletRequest,ServletResponse),其实质是BiConsumer)。无副作用的方法有很多好处:更有利于测试、构建和优化。,servletresponse>

RouterFunction

入站请求是由RouterFunction,(即Function>)路由到HandlerFunction中去的。当满足条件匹配时,路由方法会执行处理方法,否则会返回一个空结果。路由方法与@RequestMapping注解的作用相似。但是,还有一个显著的区别:用注解时路由会被限制到注解的value所能表达的范围,处理这些方法的覆盖是困难的;当用路由方法的时候,代码就在那里,可以轻松的覆盖或替换。,>

如下是一个路由方法的例子,包含了一个行内的处理方法。这里看起来有一点冗余,不必担心,因为后面我们将会将它变得精简。

12345678
RouterFunction<String> helloWorldRoute =   request -> {    if (request.path().equals("/hello-world")) {      return Optional.of(r -> Response.ok().body(fromObject("Hello World")));    } else {      return Optional.empty();    }  };

一般不用写完整的路由方法,而是静态引入RouterFunctions.route(),这样就可以用请求判断式(RequestPredicate) (即 Predicate)和处理方法(HandlerFunction)创建路由方法了。如果判断式判断成功则返回处理方法,否则返回空结果。如下是用route方法方式重写上面的例子:

123
RouterFunction<String> helloWorldRoute =  RouterFunctions.route(request -> request.path().equals("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")));

静态引入RequestPredicates.*后就可以使用那些常用的判断式了,如匹配路径、HTTP方法、content-type等。这样上面的例子将会变得更精简:

123
RouterFunction<String> helloWorldRoute =  RouterFunctions.route(RequestPredicates.path("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")));

组合函数

两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用RouterFunction.and()方法实现,如下:

12345
RouterFunction<?> route =  route(path("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")))  .and(route(path("/the-answer"),    request -> Response.ok().body(fromObject("42"))));

上面的例子如果路径匹配/hello-world会返回“Hello World”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的Optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。

请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:

12345
RouterFunction<?> route =  route(method(HttpMethod.GET).and(path("/hello-world")),     request -> Response.ok().body(fromObject("Hello World")))  .and(route(method(HttpMethod.GET).and(path("/the-answer")),     request -> Response.ok().body(fromObject("42"))));

实际上,RequestPredicates中的大部分判断式都是组合的!比如RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的组合。所以上面的例子可以重写为:

12345
RouterFunction<?> route =  route(GET("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")))  .and(route(GET("/the-answer"),    request -> Response.ok().body(fromObject(42))));

方法引用

此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:

1234567
class DemoHandler {  public Response<String> helloWorld(Request request) {    return Response.ok().body(fromObject("Hello World"));  }  public Response<String> theAnswer(Request request) {    return Response.ok().body(fromObject("42"));  }}

注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:

1234
DemoHandler handler = new DemoHandler(); // or obtain via DIRouterFunction<?> route =  route(GET("/hello-world"), handler::helloWorld)  .and(route(GET("/the-answer"), handler::theAnswer));

FilterFunction

由路由器函数进行映射的路由可以通过调用 RouterFunction.filter(FilterFunction) 来进行过滤, 这里的 FilterFunction其实就是一个 BiFunction, Response>。函数的处理器(handler)参数代表的就是整个链条中的下一项: 这是一个典型的 HandlerFunction, 但如果附加了多个过滤器的话,它也能够是另外的一个 FilterFunction。让我们向路由添加一个日志过滤器:,>,r>,>

12345678910
RouterFunction<?> route =  route(GET("/hello-world"), handler::helloWorld)  .and(route(GET("/the-answer"), handler::theAnswer))  .filter((request, next) -> {    System.out.println("Before handler invocation: " + request.path());    Response<?> response = next.handle(request);    Object body = response.body();    System.out.println("After handler invocation: " + body);    return response;  });

注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。

因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 Response<?> 结束, 那样它就会可能有一个 String 类型的响应消息体。我们可以通过使用 RouterFunction.andSame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:

12345678
RouterFunction<String> route =  route(GET("/hello-world"), handler::helloWorld)  .andSame(route(GET("/the-answer"), handler::theAnswer))  .filter((request, next) -> {    Response<String> response = next.handle(request);    String newBody = response.body().toUpperCase();    return Response.from(response).body(fromObject(newBody));  });

使用注解的话,类似的功能可以使用 @ControllerAdvice 或者是一个 ServletFilter 来实现。

运行服务器

所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 HTTP 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 RouterFunctions.toHttpHandler() 来将一个路由器函数转换成 HttpHandler。HttpHandler 是 Spring 5.0 M1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: Reactor Netty, RxNetty, Servlet 3.1+, 以及 Undertow。在本示例中,我们已经展示了在 Reactor Netty 中运行一个路由会是什么样子的。对于 Tomcat 来说则是像下面这个样子:

12345678
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);Tomcat server = new Tomcat();Context rootContext = server.addContext("",  System.getProperty("java.io.tmpdir"));Tomcat.addServlet(rootContext, "servlet", servlet);  rootContext.addServletMapping("/", "servlet");tomcatServer.start();

需要注意的意见事情就是上面的东西并不依赖于一个 Spring 应用程序上下文。就跟 JdbcTemplate 以及其它 Spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。
还要注意的就是你也可以将一个路由器函数转换到一个 HandlerMapping中去,那样就它可以在一个 DispatcherHandler (可能是跟响应式的 @Controllers 并行)中运行了。

总结

至此便结束了对 Spring 新函数式 web 框架的介绍。让我简单小结一下:

  • handler function 处理request, 返回response,
  • router function 把请求路由到handler function,并可与其他router function组合,
  • router function可以被 filter function过滤处理,
  • router function 可以运行在一个响应式web运行时框架中。

为了给你一个更全面的理解,我创建了一个简单的function web framework例子,你可以在github上找到

]]>
英文原文:New in Spring 5: Functional Web Frameworkby
中文翻译:Spring 5 新功能:函数式 Web 框架by 开源中国

就像在昨天Juergen发布的博客的一样,Spring 5.0框架第二个里程碑版本中介绍了一个新的函数式web框架。在这篇文章中,我将更详细的介绍这个框架。

紧记该函数式web框架是在Spring5.0第一个里程碑版本基础上构建的。并且我们依旧提供基于注解的请求处理(例如@Controller,@RequestMapping),关于基于注解的请求处理部分的相关信息请查阅关于Spring5.0第一个里程碑版本的博客。

示例

我们选用示例程序作为开始。下面是一个响应资源库用于暴露Person对象。这个响应资源库与传统的无响应资源库类似,除了Flux对应传统的 List,Mono对应传统的 Person对象。Mono作为完成标识:用于指示保存工作完成.更多Reactor 类型信息请查阅Dave发布的博客

1234
public interface PersonRepository {  Mono<Person> getPerson(int id);  Flux<Person> allPeople();  Mono<Void> savePerson(Mono<Person> person);}

这里我们介绍如何使用新的函数式web框架暴露资源库:

1234567891011121314151617
RouterFunction<?> route = route(GET("/person/{id}"),  request -> {    Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id"))      .map(Integer::valueOf)      .then(repository::getPerson);    return Response.ok().body(fromPublisher(person, Person.class));  })  .and(route(GET("/person"),    request -> {      Flux<Person> people = repository.allPeople();      return Response.ok().body(fromPublisher(people, Person.class));    }))  .and(route(POST("/person"),    request -> {      Mono<Person> person = request.body(toMono(Person.class));      return Response.ok().build(repository.savePerson(person));    }));

这里我们介绍如何运行它,下面是Reactor Netty的示例:

12345
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);ReactorHttpHandlerAdapter adapter =  new ReactorHttpHandlerAdapter(httpHandler);HttpServer server = HttpServer.create("localhost", 8080);server.startAndAwait(adapter);

最后要做的是,进行一次尝试请求:

12
$ curl 'http://localhost:8080/person/1'{"name":"John Doe","age":42}

上面的介绍覆盖了很多内容,下面让我们深入挖掘下!

核心组件

我将通过依次介绍HandlerFunction,RouterFunction以及FilterFunction等核心组件来介绍整个框架。这三个接口以及本文中其他类型都可以在org.springframework.web.reactive.function包中找到。

HandlerFunction

新框架的起点是HandlerFunction,其实质是Function>,其中的Request 和 Response都是新定义的不可变接口,提供了基础的对JDK8优化的HTTP消息描述DSL。有一个便捷的构造Response实例的构造器,与ResponseEntity中的十分相似。注解方式中与HandlerFunction相对应的是@RequestMapping所注解的方法。,>

如下是“Hello World”的处理方法,它返回了状态为200,body为字符串的消息。

12
HandlerFunction<String> helloWorld =  request -> Response.ok().body(fromObject("Hello World"));

如上,构建于Reactor之上的处理方法是完全的响应式的(reactive),它们可以接受Flux、Mono或者其他相应流(Reactive Streams)的发布者作为返回类型的参数。

需要注意的是处理方法本身是没有副作用的,因为它将response作为返回值,而不是作为参数(对比Servlet.service(ServletRequest,ServletResponse),其实质是BiConsumer)。无副作用的方法有很多好处:更有利于测试、构建和优化。,servletresponse>

RouterFunction

入站请求是由RouterFunction,(即Function>)路由到HandlerFunction中去的。当满足条件匹配时,路由方法会执行处理方法,否则会返回一个空结果。路由方法与@RequestMapping注解的作用相似。但是,还有一个显著的区别:用注解时路由会被限制到注解的value所能表达的范围,处理这些方法的覆盖是困难的;当用路由方法的时候,代码就在那里,可以轻松的覆盖或替换。,>

如下是一个路由方法的例子,包含了一个行内的处理方法。这里看起来有一点冗余,不必担心,因为后面我们将会将它变得精简。

12345678
RouterFunction<String> helloWorldRoute =   request -> {    if (request.path().equals("/hello-world")) {      return Optional.of(r -> Response.ok().body(fromObject("Hello World")));    } else {      return Optional.empty();    }  };

一般不用写完整的路由方法,而是静态引入RouterFunctions.route(),这样就可以用请求判断式(RequestPredicate) (即 Predicate)和处理方法(HandlerFunction)创建路由方法了。如果判断式判断成功则返回处理方法,否则返回空结果。如下是用route方法方式重写上面的例子:

123
RouterFunction<String> helloWorldRoute =  RouterFunctions.route(request -> request.path().equals("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")));

静态引入RequestPredicates.*后就可以使用那些常用的判断式了,如匹配路径、HTTP方法、content-type等。这样上面的例子将会变得更精简:

123
RouterFunction<String> helloWorldRoute =  RouterFunctions.route(RequestPredicates.path("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")));

组合函数

两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用RouterFunction.and()方法实现,如下:

12345
RouterFunction<?> route =  route(path("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")))  .and(route(path("/the-answer"),    request -> Response.ok().body(fromObject("42"))));

上面的例子如果路径匹配/hello-world会返回“Hello World”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的Optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。

请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:

12345
RouterFunction<?> route =  route(method(HttpMethod.GET).and(path("/hello-world")),     request -> Response.ok().body(fromObject("Hello World")))  .and(route(method(HttpMethod.GET).and(path("/the-answer")),     request -> Response.ok().body(fromObject("42"))));

实际上,RequestPredicates中的大部分判断式都是组合的!比如RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的组合。所以上面的例子可以重写为:

12345
RouterFunction<?> route =  route(GET("/hello-world"),    request -> Response.ok().body(fromObject("Hello World")))  .and(route(GET("/the-answer"),    request -> Response.ok().body(fromObject(42))));

方法引用

此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:

1234567
class DemoHandler {  public Response<String> helloWorld(Request request) {    return Response.ok().body(fromObject("Hello World"));  }  public Response<String> theAnswer(Request request) {    return Response.ok().body(fromObject("42"));  }}

注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:

1234
DemoHandler handler = new DemoHandler(); // or obtain via DIRouterFunction<?> route =  route(GET("/hello-world"), handler::helloWorld)  .and(route(GET("/the-answer"), handler::theAnswer));

FilterFunction

由路由器函数进行映射的路由可以通过调用 RouterFunction.filter(FilterFunction) 来进行过滤, 这里的 FilterFunction其实就是一个 BiFunction, Response>。函数的处理器(handler)参数代表的就是整个链条中的下一项: 这是一个典型的 HandlerFunction, 但如果附加了多个过滤器的话,它也能够是另外的一个 FilterFunction。让我们向路由添加一个日志过滤器:,>,r>,>

12345678910
RouterFunction<?> route =  route(GET("/hello-world"), handler::helloWorld)  .and(route(GET("/the-answer"), handler::theAnswer))  .filter((request, next) -> {    System.out.println("Before handler invocation: " + request.path());    Response<?> response = next.handle(request);    Object body = response.body();    System.out.println("After handler invocation: " + body);    return response;  });

注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。

因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 Response<?> 结束, 那样它就会可能有一个 String 类型的响应消息体。我们可以通过使用 RouterFunction.andSame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:

12345678
RouterFunction<String> route =  route(GET("/hello-world"), handler::helloWorld)  .andSame(route(GET("/the-answer"), handler::theAnswer))  .filter((request, next) -> {    Response<String> response = next.handle(request);    String newBody = response.body().toUpperCase();    return Response.from(response).body(fromObject(newBody));  });

使用注解的话,类似的功能可以使用 @ControllerAdvice 或者是一个 ServletFilter 来实现。

运行服务器

所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 HTTP 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 RouterFunctions.toHttpHandler() 来将一个路由器函数转换成 HttpHandler。HttpHandler 是 Spring 5.0 M1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: Reactor Netty, RxNetty, Servlet 3.1+, 以及 Undertow。在本示例中,我们已经展示了在 Reactor Netty 中运行一个路由会是什么样子的。对于 Tomcat 来说则是像下面这个样子:

12345678
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);Tomcat server = new Tomcat();Context rootContext = server.addContext("",  System.getProperty("java.io.tmpdir"));Tomcat.addServlet(rootContext, "servlet", servlet);  rootContext.addServletMapping("/", "servlet");tomcatServer.start();

需要注意的意见事情就是上面的东西并不依赖于一个 Spring 应用程序上下文。就跟 JdbcTemplate 以及其它 Spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。
还要注意的就是你也可以将一个路由器函数转换到一个 HandlerMapping中去,那样就它可以在一个 DispatcherHandler (可能是跟响应式的 @Controllers 并行)中运行了。

总结

至此便结束了对 Spring 新函数式 web 框架的介绍。让我简单小结一下:

  • handler function 处理request, 返回response,
  • router function 把请求路由到handler function,并可与其他router function组合,
  • router function可以被 filter function过滤处理,
  • router function 可以运行在一个响应式web运行时框架中。

为了给你一个更全面的理解,我创建了一个简单的function web framework例子,你可以在github上找到

]]>
0
<![CDATA[免费的才是最贵的]]> http://www.udpwork.com/item/16240.html http://www.udpwork.com/item/16240.html#reviews Tue, 25 Apr 2017 23:22:02 +0800 Felix021 http://www.udpwork.com/item/16240.html 服务器挂了,虽说有daily backup,然额其实数据库的备份早就挂了,手头最早的数据库回档是2016年3月份的……

没演练过恢复的备份都是假备份。

幸好还有inoreader,可以从里面找到之后发的blog。

幸好去年到今年比较懒,没有发过几篇。

慢慢修复吧。

to 有些同学:你们的评论和留言,恢复起来比较麻烦,所以就放弃了,实在抱歉。

]]>
服务器挂了,虽说有daily backup,然额其实数据库的备份早就挂了,手头最早的数据库回档是2016年3月份的……

没演练过恢复的备份都是假备份。

幸好还有inoreader,可以从里面找到之后发的blog。

幸好去年到今年比较懒,没有发过几篇。

慢慢修复吧。

to 有些同学:你们的评论和留言,恢复起来比较麻烦,所以就放弃了,实在抱歉。

]]>
0
<![CDATA[My Nexus 5X bullhead is gone for good]]> http://www.udpwork.com/item/16227.html http://www.udpwork.com/item/16227.html#reviews Tue, 25 Apr 2017 18:30:00 +0800 IT牛人.117 http://www.udpwork.com/item/16227.html TL;DR

The 13 months old Nexus 5X 32GB entered an infinite reboot loop, it has gone for good. LG != Life’s Good, it stands for Low (quality, standards) Goods really, avoid at any cost.

NOTE: My previous Nexus 5 became unusable after the power button issue. It turned off on its own randonly and powering it on can be very challenging - keep pressing the power button as fast as you can -_-z

Story

It was public holiday in Australia - Anzac Day, supposed to be a good day but thehammerheadruined my afternoon and night.

It all started with a rebooted after sending some photos to family members. I saw the boot passphrase prompt (full disk encryption enabled), touch screen did not respond so I powered it off, it then entered the reboot loop. It was able enterbootloaderbut failed to boot intoTWRP.

This is the first time I’ve seen such a bad reboot loop (there were reboot loops before but not as bad). I knew something was seriously wrong. A Google Search revealed that it was a common hardware defect for some LG devices. There is aclass-action lawsuit targeting LG in California, Nexus 5X was just added a couple of weeks ago, sigh…

I don’t really care about refund or repair but the 2 weeks worth of photos that I failed to backup. It seems that the only possible way is to take the flash memory unit out and use a specialised device to connect to a Linux machine, hopefully Linux can deal with the full disk encryption based ondm-crypt/LUKS, mount the concerned filesystem and extract important data.

After I am done with the bullhead, I’ll give it a few hammers and smash it for good.

My next mobile? I’ve thought about it. No more Samsung or LG, Sony is not my type, Pixel is now as expensive as latest iPhone models, I really cannot justify spending A$1200+ on a Google Pixel (made by HTC, I’ve seen the build quality, no good). So the only option is an iPhone 7 Plus for now -_-z

Anyway, my current requirements for a mobile

  • Reliable hardware and high quality finish
  • Strong hardware based/accelerated encryption
  • Fingerprint recognition and authentication
  • Stable and Swift OS (greatly improved withAPFS, regained faith in Apple)
  • Good camera - the best camera is the one with you
  • Long battery life (ideally support both fast charge and wireless charge)

NOTE: I don’t needrooton a mobile device any more, it was mainly required to fix some legacy Android problems and to have full control of the device OS.

So I’ll probably go get one tomorrow.

Goodbye Google Nexus->Nexus{S,4,5,5X}, most likely the same to Android.

I don’t have time to deal with Google’s problems.

IMPORTANT: Backup and look for your next phone if you are still using Nexus 5X or Nexus 6P.

UPDATE : 2017-04-26 I managed to boot into the system after 20+ attempts, lucky enough to backup all new photos. Shortly after it rebooted again and entered another reboot loop.

]]>
TL;DR

The 13 months old Nexus 5X 32GB entered an infinite reboot loop, it has gone for good. LG != Life’s Good, it stands for Low (quality, standards) Goods really, avoid at any cost.

NOTE: My previous Nexus 5 became unusable after the power button issue. It turned off on its own randonly and powering it on can be very challenging - keep pressing the power button as fast as you can -_-z

Story

It was public holiday in Australia - Anzac Day, supposed to be a good day but thehammerheadruined my afternoon and night.

It all started with a rebooted after sending some photos to family members. I saw the boot passphrase prompt (full disk encryption enabled), touch screen did not respond so I powered it off, it then entered the reboot loop. It was able enterbootloaderbut failed to boot intoTWRP.

This is the first time I’ve seen such a bad reboot loop (there were reboot loops before but not as bad). I knew something was seriously wrong. A Google Search revealed that it was a common hardware defect for some LG devices. There is aclass-action lawsuit targeting LG in California, Nexus 5X was just added a couple of weeks ago, sigh…

I don’t really care about refund or repair but the 2 weeks worth of photos that I failed to backup. It seems that the only possible way is to take the flash memory unit out and use a specialised device to connect to a Linux machine, hopefully Linux can deal with the full disk encryption based ondm-crypt/LUKS, mount the concerned filesystem and extract important data.

After I am done with the bullhead, I’ll give it a few hammers and smash it for good.

My next mobile? I’ve thought about it. No more Samsung or LG, Sony is not my type, Pixel is now as expensive as latest iPhone models, I really cannot justify spending A$1200+ on a Google Pixel (made by HTC, I’ve seen the build quality, no good). So the only option is an iPhone 7 Plus for now -_-z

Anyway, my current requirements for a mobile

  • Reliable hardware and high quality finish
  • Strong hardware based/accelerated encryption
  • Fingerprint recognition and authentication
  • Stable and Swift OS (greatly improved withAPFS, regained faith in Apple)
  • Good camera - the best camera is the one with you
  • Long battery life (ideally support both fast charge and wireless charge)

NOTE: I don’t needrooton a mobile device any more, it was mainly required to fix some legacy Android problems and to have full control of the device OS.

So I’ll probably go get one tomorrow.

Goodbye Google Nexus->Nexus{S,4,5,5X}, most likely the same to Android.

I don’t have time to deal with Google’s problems.

IMPORTANT: Backup and look for your next phone if you are still using Nexus 5X or Nexus 6P.

UPDATE : 2017-04-26 I managed to boot into the system after 20+ attempts, lucky enough to backup all new photos. Shortly after it rebooted again and entered another reboot loop.

]]>
0
<![CDATA[谈谈推荐排序]]> http://www.udpwork.com/item/16226.html http://www.udpwork.com/item/16226.html#reviews Tue, 25 Apr 2017 16:37:28 +0800 老王 http://www.udpwork.com/item/16226.html 本文说的排序并不是指「冒泡」之类的技术概念,而是一个业务相关的问题。

举例来说:某个网站,每天都能产生很多数据,需要一个推荐列表页面来展示数据。最初是完全按照时间倒序来排序的,但是这样就产生了一个问题:新鲜的数据不一定是有价值的数据!假设某个时段灌水的数据比较多,那么用户当时在列表页看到的就都是灌水的内容。既然如此,不妨换个思路:给每个数据投票,投票规则可以按业务逻辑自定义,比如:每次评论加一票,每次转发加两票等等。然后按照投票数来倒序是不是就可以了?可惜还有问题:有价值的数据不一定是新鲜的数据!假设历史上曾经产生了一个高票的数据,那么不管过多久,它都会一直占据前排座位。由此可见最好的结果是让用户能看到既新鲜又有价值的数据。

实际上此问题早就有人讨论过了,建议大家阅读相关文章:

一种方法是让分数随着时间的流逝而降低,比如说一条数据原本的分数是 100 分,第二天衰减到 50 分,第三天衰减到 25 分,以此类推。此方法的优点是简单易懂,缺点是数据的分数随着时间的变化而变化,实际应用中,可能需要用 cron 之类的方法定期更新数据的分数,一旦数据比较多就悲剧了。

另一种方法是让分数本身就包含时间因子:「t / 43200 + log10(x)」:

  • 「t」表示的是数据生成时的时间戳。
  • 「43200」表示的是半天(12 个小时)的秒数。
  • 「x」表示的是数据得到的票数。

于是乎可知「t / 43200」得到的就是有多少个半天:

<?php

$result = [];

$dates = [
    '2017-01-01',
    '2017-01-02',
    '2017-01-03',
    '2017-01-04',
    '2017-01-05',
];

foreach ($dates as $date) {
    $result[$date] = strtotime($date) / 43200;
}

var_export($result);

/*

结果:

array (
  '2017-01-01' => 34334,
  '2017-01-02' => 34336,
  '2017-01-03' => 34338,
  '2017-01-04' => 34340,
  '2017-01-05' => 34342,
)

*/

?>

实际上「t / 43200」相当于是「时间分」,随着时间的增加而增加,其基数比较大,保证了新鲜的数据会排在前面,同时每天「时间分」的差值为 2,后面会用到这个数字。

那「log10(x)」又是什么意思呢,我们可以把它理解成「价值分」,其基数比较小,之所以做 log10 的对数运算就是为了快速衰减,确保前面投票的权重大于后面投票,如此有利于让有价值的数据尽早被发现:

<?php

$result = [];

$values = [
    10000,
    1000,
    100,
];

foreach ($values as $value) {
    $result[$value] = sprintf('%.5f', log10($value));
}

var_export($result);

/*

结果:

array (
  10000 => '4.00000',
  1000 => '3.00000',
  100 => '2.00000',
)

*/

?>

如上所示:如果一条数据的得票数是 100,那么它的价值分将是 2,最多可以在推荐列表上停留一天;如果一条数据的得票数是 1000,那么它的价值分将是 3,最多可以在推荐列表上停留一天半;如果一条数据的得票数是 10000,那么它的价值分将是 4,最多可以在推荐列表上停留两天,如此保证价值分高的数据不会霸占前排座位太久。

最后要说明的是,与其死记硬背公式,倒不如理解它的意思,并根据客观情况调整,比如说,有的人觉得时间分本身太大了,那么我们可以减去一个参照时间。有的人觉得为什么一定要除以 12 个小时,除以 12.5 个小时可不可以?答案当然是可以的!有的人觉得为什么一定要用以 10 为底的对数,可不可以使用科学对数?答案当然也是可以的!只要你理解了分数中「时间分」和「价值分」的用意,那么怎么实现它们都可以。

]]>
本文说的排序并不是指「冒泡」之类的技术概念,而是一个业务相关的问题。

举例来说:某个网站,每天都能产生很多数据,需要一个推荐列表页面来展示数据。最初是完全按照时间倒序来排序的,但是这样就产生了一个问题:新鲜的数据不一定是有价值的数据!假设某个时段灌水的数据比较多,那么用户当时在列表页看到的就都是灌水的内容。既然如此,不妨换个思路:给每个数据投票,投票规则可以按业务逻辑自定义,比如:每次评论加一票,每次转发加两票等等。然后按照投票数来倒序是不是就可以了?可惜还有问题:有价值的数据不一定是新鲜的数据!假设历史上曾经产生了一个高票的数据,那么不管过多久,它都会一直占据前排座位。由此可见最好的结果是让用户能看到既新鲜又有价值的数据。

实际上此问题早就有人讨论过了,建议大家阅读相关文章:

一种方法是让分数随着时间的流逝而降低,比如说一条数据原本的分数是 100 分,第二天衰减到 50 分,第三天衰减到 25 分,以此类推。此方法的优点是简单易懂,缺点是数据的分数随着时间的变化而变化,实际应用中,可能需要用 cron 之类的方法定期更新数据的分数,一旦数据比较多就悲剧了。

另一种方法是让分数本身就包含时间因子:「t / 43200 + log10(x)」:

  • 「t」表示的是数据生成时的时间戳。
  • 「43200」表示的是半天(12 个小时)的秒数。
  • 「x」表示的是数据得到的票数。

于是乎可知「t / 43200」得到的就是有多少个半天:

<?php

$result = [];

$dates = [
    '2017-01-01',
    '2017-01-02',
    '2017-01-03',
    '2017-01-04',
    '2017-01-05',
];

foreach ($dates as $date) {
    $result[$date] = strtotime($date) / 43200;
}

var_export($result);

/*

结果:

array (
  '2017-01-01' => 34334,
  '2017-01-02' => 34336,
  '2017-01-03' => 34338,
  '2017-01-04' => 34340,
  '2017-01-05' => 34342,
)

*/

?>

实际上「t / 43200」相当于是「时间分」,随着时间的增加而增加,其基数比较大,保证了新鲜的数据会排在前面,同时每天「时间分」的差值为 2,后面会用到这个数字。

那「log10(x)」又是什么意思呢,我们可以把它理解成「价值分」,其基数比较小,之所以做 log10 的对数运算就是为了快速衰减,确保前面投票的权重大于后面投票,如此有利于让有价值的数据尽早被发现:

<?php

$result = [];

$values = [
    10000,
    1000,
    100,
];

foreach ($values as $value) {
    $result[$value] = sprintf('%.5f', log10($value));
}

var_export($result);

/*

结果:

array (
  10000 => '4.00000',
  1000 => '3.00000',
  100 => '2.00000',
)

*/

?>

如上所示:如果一条数据的得票数是 100,那么它的价值分将是 2,最多可以在推荐列表上停留一天;如果一条数据的得票数是 1000,那么它的价值分将是 3,最多可以在推荐列表上停留一天半;如果一条数据的得票数是 10000,那么它的价值分将是 4,最多可以在推荐列表上停留两天,如此保证价值分高的数据不会霸占前排座位太久。

最后要说明的是,与其死记硬背公式,倒不如理解它的意思,并根据客观情况调整,比如说,有的人觉得时间分本身太大了,那么我们可以减去一个参照时间。有的人觉得为什么一定要除以 12 个小时,除以 12.5 个小时可不可以?答案当然是可以的!有的人觉得为什么一定要用以 10 为底的对数,可不可以使用科学对数?答案当然也是可以的!只要你理解了分数中「时间分」和「价值分」的用意,那么怎么实现它们都可以。

]]>
0
<![CDATA[熵:宇宙的终极规则]]> http://www.udpwork.com/item/16225.html http://www.udpwork.com/item/16225.html#reviews Sun, 23 Apr 2017 21:23:20 +0800 阮一峰 http://www.udpwork.com/item/16225.html 1、

有人曾经问我:"成年后,有没有书籍改变过你的世界观?"

我想了想,还真有这样的书。那时,我已经工作好几年了,偶然在图书馆翻到一本旧书《熵:一种新的世界观》(上海译文出版社,1987)。

那本书是科普著作,介绍物理学概念"熵"。中学毕业后,我再没有碰过物理学,但是没想到读完以后,我看待世界的眼光都变了。

"熵"这个概念非常简单,很容易理解,但又异常强大,可以解释很多事情。这篇文章,我就来谈谈,为什么你应该懂得熵是什么,它可能也会改变你的世界观。

2、

为了理解熵,必须讲一点物理学。

19世纪,物理学家开始认识到,世界的动力是能量,并且提出"能量守恒定律",即能量的总和是不变的。但是,有一个现象让他们很困惑。

(上图中,单摆在两侧的最高点,势能最大,动能为零;在中间的低点,动能最大,势能为零,能量始终守恒。)

物理学家发现,能量无法百分百地转换。比如,蒸汽机使用的是热能,将其转换为推动机器的机械能。这个过程中,总是有一些热能损耗掉,无法完全转变为机械能。

(上图中,能量 E 的转换,总是会导致能量损耗 ∆E。)

一开始,物理学家以为是技术水平不高导致的,但后来发现,技术再进步,也无法将能量损耗降到零。他们就将那些在能量转换过程中浪费掉的、无法再利用的能量称为熵。

后来,这个概念被总结成了"热力学第二定律":能量转换总是会产生熵,如果是封闭系统,所有能量最终都会变成熵。

3、

熵既然是能量,为什么无法利用?它又是怎么产生的?为什么所有能量最后都会变成熵?这些问题我想了很久。

物理学家有很多种解释,有一种我觉得最容易懂:能量转换的时候,大部分能量会转换成预先设定的状态,比如热能变成机械能、电能变成光能。但是,就像细胞突变那样,还有一部分能量会生成新的状态。这部分能量就是熵,由于状态不同,所以很难利用,除非外部注入新的能量,专门处理熵。

(上图,能量转换过程中,创造出许多新状态。)

总之,能量转换会创造出新的状态,熵就是进入这些状态的能量。

4、

现在请大家思考:状态多意味着什么?

状态多,就是可能性多,表示比较混乱;状态少,就是可能性少,相对来说就比较有秩序。因此,上面结论的另一种表达是:能量转换会让系统的混乱度增加,熵就是系统的混乱度。

(上图中,熵低则混乱度低,熵高则混乱度高。)

转换的能量越大,创造出来的新状态就会越多,因此高能量系统不如低能量系统稳定,因为前者的熵较大。而且,凡是运动的系统都会有能量转换,热力学第二定律就是在说,所有封闭系统最终都会趋向混乱度最大的状态,除非外部注入能量。

(上图中,冰块是分子的有序排列,吸收能量后,变成液体水,分子排列变得无序。)

5、

熵让我理解了一件事,如果不施加外力影响,事物永远向着更混乱的状态发展。 比如,房间如果没人打扫,只会越来越乱,不可能越来越干净。

(上图中,如果不花费能量打扫,房间总是越来越乱。)

为什么"世间好物不坚牢,彩云易散琉璃脆"?就是因为事物维持美好的状态是需要能量的,如果没有能量输入,美好的状态就会结束。

这就是我世界观的变化。我从此认识到,人类社会并非一定会变得更进步、更文明。相反地,人类如同宇宙的其他事物一样,常态和最终命运一定是变得更混乱和无序。过去五千年,人类文明的进步只是因为人类学会利用外部能量(牲畜、火种、水力等等)。越来越多的能量注入,使得人类社会向着文明有序的方向发展。

(上图中,经过130多亿年的膨胀,宇宙变得越来越无序了。)

6、

工业革命以后,人类社会的进步速度加快了,变得更加先进有序,消耗的能量也指数级地增长:水力不够了用煤炭,煤炭不够了用石油,石油不够了用核能。

能量消耗越大,就会产生越多的熵。因此,人类社会始终处于一种矛盾状态:整个社会变得更加有序和严密的同时,无序和混乱也在暗处不断滋长。

我们只是依靠更大的能量输入,在压制熵的累积。不断增加的熵,正在各种方面爆发出来:垃圾污染、地球变暖、土地沙化、PM2.5、物种灭绝......甚至心理疾病、孤独感和疏离感的暴增,我认为都是熵的增加对人类精神造成的结果。

我们需要能量,让世界变得有秩序,但这样是有代价的。物理学告诉我们,没有办法消除熵和混乱,我们只是让某些局部变得更有秩序,把混乱转移到另一些领域。

7、

人类社会正在加速发展。表面上,我们正在经历一个减熵过程,一切变得越来越有秩序,自动化带来了便捷。但是,能量消耗也在同步放大,为了解决越来越多的熵,我们不得不寻找更多的能量,这又导致熵的进一步增加,从而陷入恶性循环。

迄今为止,人类一直能够找到足够的能量,解决熵带来的混乱。但是,这种解决方式正变得捉襟见肘。如果我们继续像现在这样加速发展,那么终有一天会出现能量缺口,地球上的能量不足以解决熵,那时一切就会发生逆转,仿佛细小的裂缝演变成巨大的雪崩,秩序开始崩塌,世界走向混乱。

([说明] 本文出自我正在写的新书《未来世界的幸存者》,点击这里免费阅读全书。)

(完)

文档信息

]]>
1、

有人曾经问我:"成年后,有没有书籍改变过你的世界观?"

我想了想,还真有这样的书。那时,我已经工作好几年了,偶然在图书馆翻到一本旧书《熵:一种新的世界观》(上海译文出版社,1987)。

那本书是科普著作,介绍物理学概念"熵"。中学毕业后,我再没有碰过物理学,但是没想到读完以后,我看待世界的眼光都变了。

"熵"这个概念非常简单,很容易理解,但又异常强大,可以解释很多事情。这篇文章,我就来谈谈,为什么你应该懂得熵是什么,它可能也会改变你的世界观。

2、

为了理解熵,必须讲一点物理学。

19世纪,物理学家开始认识到,世界的动力是能量,并且提出"能量守恒定律",即能量的总和是不变的。但是,有一个现象让他们很困惑。

(上图中,单摆在两侧的最高点,势能最大,动能为零;在中间的低点,动能最大,势能为零,能量始终守恒。)

物理学家发现,能量无法百分百地转换。比如,蒸汽机使用的是热能,将其转换为推动机器的机械能。这个过程中,总是有一些热能损耗掉,无法完全转变为机械能。

(上图中,能量 E 的转换,总是会导致能量损耗 ∆E。)

一开始,物理学家以为是技术水平不高导致的,但后来发现,技术再进步,也无法将能量损耗降到零。他们就将那些在能量转换过程中浪费掉的、无法再利用的能量称为熵。

后来,这个概念被总结成了"热力学第二定律":能量转换总是会产生熵,如果是封闭系统,所有能量最终都会变成熵。

3、

熵既然是能量,为什么无法利用?它又是怎么产生的?为什么所有能量最后都会变成熵?这些问题我想了很久。

物理学家有很多种解释,有一种我觉得最容易懂:能量转换的时候,大部分能量会转换成预先设定的状态,比如热能变成机械能、电能变成光能。但是,就像细胞突变那样,还有一部分能量会生成新的状态。这部分能量就是熵,由于状态不同,所以很难利用,除非外部注入新的能量,专门处理熵。

(上图,能量转换过程中,创造出许多新状态。)

总之,能量转换会创造出新的状态,熵就是进入这些状态的能量。

4、

现在请大家思考:状态多意味着什么?

状态多,就是可能性多,表示比较混乱;状态少,就是可能性少,相对来说就比较有秩序。因此,上面结论的另一种表达是:能量转换会让系统的混乱度增加,熵就是系统的混乱度。

(上图中,熵低则混乱度低,熵高则混乱度高。)

转换的能量越大,创造出来的新状态就会越多,因此高能量系统不如低能量系统稳定,因为前者的熵较大。而且,凡是运动的系统都会有能量转换,热力学第二定律就是在说,所有封闭系统最终都会趋向混乱度最大的状态,除非外部注入能量。

(上图中,冰块是分子的有序排列,吸收能量后,变成液体水,分子排列变得无序。)

5、

熵让我理解了一件事,如果不施加外力影响,事物永远向着更混乱的状态发展。 比如,房间如果没人打扫,只会越来越乱,不可能越来越干净。

(上图中,如果不花费能量打扫,房间总是越来越乱。)

为什么"世间好物不坚牢,彩云易散琉璃脆"?就是因为事物维持美好的状态是需要能量的,如果没有能量输入,美好的状态就会结束。

这就是我世界观的变化。我从此认识到,人类社会并非一定会变得更进步、更文明。相反地,人类如同宇宙的其他事物一样,常态和最终命运一定是变得更混乱和无序。过去五千年,人类文明的进步只是因为人类学会利用外部能量(牲畜、火种、水力等等)。越来越多的能量注入,使得人类社会向着文明有序的方向发展。

(上图中,经过130多亿年的膨胀,宇宙变得越来越无序了。)

6、

工业革命以后,人类社会的进步速度加快了,变得更加先进有序,消耗的能量也指数级地增长:水力不够了用煤炭,煤炭不够了用石油,石油不够了用核能。

能量消耗越大,就会产生越多的熵。因此,人类社会始终处于一种矛盾状态:整个社会变得更加有序和严密的同时,无序和混乱也在暗处不断滋长。

我们只是依靠更大的能量输入,在压制熵的累积。不断增加的熵,正在各种方面爆发出来:垃圾污染、地球变暖、土地沙化、PM2.5、物种灭绝......甚至心理疾病、孤独感和疏离感的暴增,我认为都是熵的增加对人类精神造成的结果。

我们需要能量,让世界变得有秩序,但这样是有代价的。物理学告诉我们,没有办法消除熵和混乱,我们只是让某些局部变得更有秩序,把混乱转移到另一些领域。

7、

人类社会正在加速发展。表面上,我们正在经历一个减熵过程,一切变得越来越有秩序,自动化带来了便捷。但是,能量消耗也在同步放大,为了解决越来越多的熵,我们不得不寻找更多的能量,这又导致熵的进一步增加,从而陷入恶性循环。

迄今为止,人类一直能够找到足够的能量,解决熵带来的混乱。但是,这种解决方式正变得捉襟见肘。如果我们继续像现在这样加速发展,那么终有一天会出现能量缺口,地球上的能量不足以解决熵,那时一切就会发生逆转,仿佛细小的裂缝演变成巨大的雪崩,秩序开始崩塌,世界走向混乱。

([说明] 本文出自我正在写的新书《未来世界的幸存者》,点击这里免费阅读全书。)

(完)

文档信息

]]>
0
<![CDATA[ssh两步验证强化版(publickey+google authenticator)]]> http://www.udpwork.com/item/16239.html http://www.udpwork.com/item/16239.html#reviews Sun, 23 Apr 2017 00:15:37 +0800 Felix021 http://www.udpwork.com/item/16239.html 两年前学会了用Google Authenticator(详见为SSH添加两步验证),远程服务就装上了,感觉放心了很多。

但当时只是简单加上了Google Authenticator,实际使用中既要输入验证又要输入密码,太繁琐了,所以在搭建我司跳板机的时候,选择了用 publickey + authenticator 的方案,只需要输入一次验证码即可。

具体的配置方案变化不大,主要是用上了 SSH 6.2+ 新增的 AuthenticationMethods 参数,可以指定一系列验证方法,具体配置如下:

引用
#默认需要先用publickey验证,再用验证码
AuthenticationMethods publickey,keyboard-interactive

#对于指定的IP,只需要publickey验证
Match Address 10.0.0.4
    AuthenticationMethods publickey

#也可以指定用户只需要publickey验证
#Match User XXX
    #AuthenticationMethods publickey


顺便吐槽一下,Linux这套东西折腾起来真是要命,今天配置跳板机备份机的时候,完全相同的配置,复制一份就是不对,虽然配置里只指定了publickey,keyboard-interactive,但是每次输完验证码以后还是要求输入密码才行,折腾了几个小时才发现,不知道从什么时候开始,"auth required pam_google_authenticator.so" 已经不合适了,需要改成 "auth sufficient pam_google_authenticator.so",这样才会在输入验证码以后就结束认证过程(sufficient的实现里加了一个break?什么鬼。)(感谢 @https://serverfault.com/a/740881/343388

以及,上篇也提到过的,另外一个坑是 ubuntu/debian 下面在自己编译完pam模块以后,需要手动拷贝到 /lib/security/ 目录下面才行,唉…… ]]>
两年前学会了用Google Authenticator(详见为SSH添加两步验证),远程服务就装上了,感觉放心了很多。

但当时只是简单加上了Google Authenticator,实际使用中既要输入验证又要输入密码,太繁琐了,所以在搭建我司跳板机的时候,选择了用 publickey + authenticator 的方案,只需要输入一次验证码即可。

具体的配置方案变化不大,主要是用上了 SSH 6.2+ 新增的 AuthenticationMethods 参数,可以指定一系列验证方法,具体配置如下:

引用
#默认需要先用publickey验证,再用验证码
AuthenticationMethods publickey,keyboard-interactive

#对于指定的IP,只需要publickey验证
Match Address 10.0.0.4
    AuthenticationMethods publickey

#也可以指定用户只需要publickey验证
#Match User XXX
    #AuthenticationMethods publickey


顺便吐槽一下,Linux这套东西折腾起来真是要命,今天配置跳板机备份机的时候,完全相同的配置,复制一份就是不对,虽然配置里只指定了publickey,keyboard-interactive,但是每次输完验证码以后还是要求输入密码才行,折腾了几个小时才发现,不知道从什么时候开始,"auth required pam_google_authenticator.so" 已经不合适了,需要改成 "auth sufficient pam_google_authenticator.so",这样才会在输入验证码以后就结束认证过程(sufficient的实现里加了一个break?什么鬼。)(感谢 @https://serverfault.com/a/740881/343388

以及,上篇也提到过的,另外一个坑是 ubuntu/debian 下面在自己编译完pam模块以后,需要手动拷贝到 /lib/security/ 目录下面才行,唉…… ]]>
0
<![CDATA[Xmemcached源码阅读]]> http://www.udpwork.com/item/16224.html http://www.udpwork.com/item/16224.html#reviews Sun, 23 Apr 2017 00:00:00 +0800 Kevin Lynx http://www.udpwork.com/item/16224.html Xmemcached是一个memcached客户端库。由于它提供的是同步API,而我想看下如何增加异步接口。所以就大致浏览了下它的源码。

主要结构

针对memcache客户端的实现,主要结构如下:

  • XMemcachedClient是应用主要使用的类,所有针对memcache的接口都在这里
  • Command用于抽象二进制协议或文本协议下各个操作,这里称为Command。CommandFactory用于创建这些command
  • MemcachedSessionLocator用于抽象不同的负载均衡策略,或者说数据分布策略。在一个memcached集群中,数据具体存放在哪个replica中,主要就是由这个类的实现具体的,例如KetamaMemcachedSessionLocator实现了一致性哈希策略
  • MemcachedConnector包装了网络部分,与每一个memcached建立连接后,就得到一个Session。command的发送都在MemcachedConnector中实现
  • 各个Session类/接口,则涉及到Xmemcached使用的网络库yanf4j。这个库也是Xmemcached作者的。

Command 类的实现中有个关键的CountDownLatch。在将Command通过session发送出去之后,就利用这个latch同步等待,等到网络模块收到数据后回调。Command会和session绑定,在这个session上收到数据后,就认为是这个command的回应。

由于本身memcached库核心东西比较少,上面的结构也就很好理解。协议的抽象和数据分布策略的抽象是必须的。接下来看看网络实现部分。

网络实现

Xmemcached的网络实现主要结构如下:

  • SocketChannelController,主要的类,将IO事件通知转交给session
  • NioController,主要关注其成员SelectorManagrer
  • SelectorManager内置若干个Reactor,数量由CPU核数决定
  • Reactor,IO事件的产生器,一个Reactor对应一个线程,线程循环中不断轮询NIO selector是否产生了IO事件
  • CodecFactory,编解码网络消息接口
  • PoolDispatcher,Dispatcher 用于调度一个IO事件的具体处理过程,而PoolDispatcher则是放到一个单独的线程池中处理
  • DispatcherFactory,用于创建具体的dispatcher

这个网络实现还是比较典型的Reactor模式。其中,产生IO事件后,IO事件的具体处理,默认交给了一个独立的线程池。一般网络库都会提供类似的机制,以使得IO线程不至于被业务逻辑阻塞住,导致IO处理效率下降。

写数据时,数据都会写到一个队列中,在设备可写时才具体写入。看下具体的读数据过程:

从Reactor中最终调用到Xmemcached的command,用于具体解析回应数据。要调整为异步的话,则可以修改Command的实现,增加异步回调。同时注意控制dispatcher使用的线程池。

完。

原文地址:http://codemacro.com/2017/04/23/xmemcached/
written byKevin Lynx posted athttp://codemacro.com

]]>
Xmemcached是一个memcached客户端库。由于它提供的是同步API,而我想看下如何增加异步接口。所以就大致浏览了下它的源码。

主要结构

针对memcache客户端的实现,主要结构如下:

  • XMemcachedClient是应用主要使用的类,所有针对memcache的接口都在这里
  • Command用于抽象二进制协议或文本协议下各个操作,这里称为Command。CommandFactory用于创建这些command
  • MemcachedSessionLocator用于抽象不同的负载均衡策略,或者说数据分布策略。在一个memcached集群中,数据具体存放在哪个replica中,主要就是由这个类的实现具体的,例如KetamaMemcachedSessionLocator实现了一致性哈希策略
  • MemcachedConnector包装了网络部分,与每一个memcached建立连接后,就得到一个Session。command的发送都在MemcachedConnector中实现
  • 各个Session类/接口,则涉及到Xmemcached使用的网络库yanf4j。这个库也是Xmemcached作者的。

Command 类的实现中有个关键的CountDownLatch。在将Command通过session发送出去之后,就利用这个latch同步等待,等到网络模块收到数据后回调。Command会和session绑定,在这个session上收到数据后,就认为是这个command的回应。

由于本身memcached库核心东西比较少,上面的结构也就很好理解。协议的抽象和数据分布策略的抽象是必须的。接下来看看网络实现部分。

网络实现

Xmemcached的网络实现主要结构如下:

  • SocketChannelController,主要的类,将IO事件通知转交给session
  • NioController,主要关注其成员SelectorManagrer
  • SelectorManager内置若干个Reactor,数量由CPU核数决定
  • Reactor,IO事件的产生器,一个Reactor对应一个线程,线程循环中不断轮询NIO selector是否产生了IO事件
  • CodecFactory,编解码网络消息接口
  • PoolDispatcher,Dispatcher 用于调度一个IO事件的具体处理过程,而PoolDispatcher则是放到一个单独的线程池中处理
  • DispatcherFactory,用于创建具体的dispatcher

这个网络实现还是比较典型的Reactor模式。其中,产生IO事件后,IO事件的具体处理,默认交给了一个独立的线程池。一般网络库都会提供类似的机制,以使得IO线程不至于被业务逻辑阻塞住,导致IO处理效率下降。

写数据时,数据都会写到一个队列中,在设备可写时才具体写入。看下具体的读数据过程:

从Reactor中最终调用到Xmemcached的command,用于具体解析回应数据。要调整为异步的话,则可以修改Command的实现,增加异步回调。同时注意控制dispatcher使用的线程池。

完。

原文地址:http://codemacro.com/2017/04/23/xmemcached/
written byKevin Lynx posted athttp://codemacro.com

]]>
0
<![CDATA[再来两个猴子]]> http://www.udpwork.com/item/16223.html http://www.udpwork.com/item/16223.html#reviews Sat, 22 Apr 2017 19:46:19 +0800 qyjohn http://www.udpwork.com/item/16223.html thumb_IMG_2860_1024thumb_IMG_2861_1024thumb_IMG_2863_1024

]]>
thumb_IMG_2860_1024thumb_IMG_2861_1024thumb_IMG_2863_1024

]]>
0
<![CDATA[猴子]]> http://www.udpwork.com/item/16222.html http://www.udpwork.com/item/16222.html#reviews Sat, 22 Apr 2017 18:50:05 +0800 qyjohn http://www.udpwork.com/item/16222.html thumb_IMG_2856_1024thumb_IMG_2857_1024thumb_IMG_2858_1024thumb_IMG_2859_1024

]]>
thumb_IMG_2856_1024thumb_IMG_2857_1024thumb_IMG_2858_1024thumb_IMG_2859_1024

]]>
0
<![CDATA[sudo玩弄小记]]> http://www.udpwork.com/item/16229.html http://www.udpwork.com/item/16229.html#reviews Sat, 22 Apr 2017 14:25:32 +0800 Felix021 http://www.udpwork.com/item/16229.html 在我司的运维实践中,sudo承担了一个很边缘,但是却很有意思的任务

最简单应用是,在web服务器上,在配置完nginx和php的log以后需要重启service,就这么玩(修改/etc/sudoers):

引用
nginx  ALL= NOPASSWD: /sbin/service nginx *, /sbin/service php5-fpm reload

如此一来,nginx用户可以执行 sudo service nginx reload 或者 sudo nginx configtest,也可以执行 sudo php5-fpm reload,但不能执行 sudo php5-fpm stop

不过被玩出花来的还是我司的跳板机。

对于管理员,我们这样配置:
引用
felix021 ALL=(ALL:ALL) ALL

通过密码验证切换到root用户

对于组长,我们这样配置:

  $ sudo groupadd master #添加组
  $ sudo usermod -a -G master felix021 #将用户添加到这个组
  $ vi /etc/sudoers
引用
%master ALL= NOPASSWD: /usr/bin/getpubkey *

这个 getpubkey 是一个shell脚本,只包含一句 cat "/home/$user/.ssh/id_rsa.pub" ,用来获取某个用户的公钥

然后配合 authroize_user 这个脚本,将公钥发送到组长有权限访问的机器上,通过这种方式实现一个简陋的二级授权:
引用
sudo getpubkey $1 | ssh nginx@$host bash -c "cat /dev/stdin >> ~/.ssh/authorized_keys"


另外顺便提一下sudo的debug,似乎相关资料很少。在配置group的时候,直接去测试,有时会发现好像总是不对,但是sudo默认没有log,很难排查问题。实际上只要在 /etc/ 下面添加一个 sudo.conf 就好,文件内容为:
引用
Debug sudo /var/log/sudo_debug all@warn,plugin@info

再次执行sudo的时候就会看到debug log文件里的信息:
引用
Apr 22 14:12:03 sudo[21786] user_info: user=felix021
Apr 22 14:12:03 sudo[21786] user_info: pid=21786
Apr 22 14:12:03 sudo[21786] user_info: ppid=21785
Apr 22 14:12:03 sudo[21786] user_info: pgid=21785
Apr 22 14:12:03 sudo[21786] user_info: tcpgid=21785
Apr 22 14:12:03 sudo[21786] user_info: sid=21522
Apr 22 14:12:03 sudo[21786] user_info: uid=1002
Apr 22 14:12:03 sudo[21786] user_info: euid=0
Apr 22 14:12:03 sudo[21786] user_info: gid=1000
Apr 22 14:12:03 sudo[21786] user_info: egid=1000
Apr 22 14:12:03 sudo[21786] user_info: groups=1000

从这里可以看到,虽然前面把 felix021 添加到了 master 这个group下,但是sudo并没有识别出来。

放狗搜了一下才知道,原来linux下需要退出所有的登录session重新登录,才能生效。
]]>
在我司的运维实践中,sudo承担了一个很边缘,但是却很有意思的任务

最简单应用是,在web服务器上,在配置完nginx和php的log以后需要重启service,就这么玩(修改/etc/sudoers):

引用
nginx  ALL= NOPASSWD: /sbin/service nginx *, /sbin/service php5-fpm reload

如此一来,nginx用户可以执行 sudo service nginx reload 或者 sudo nginx configtest,也可以执行 sudo php5-fpm reload,但不能执行 sudo php5-fpm stop

不过被玩出花来的还是我司的跳板机。

对于管理员,我们这样配置:
引用
felix021 ALL=(ALL:ALL) ALL

通过密码验证切换到root用户

对于组长,我们这样配置:

  $ sudo groupadd master #添加组
  $ sudo usermod -a -G master felix021 #将用户添加到这个组
  $ vi /etc/sudoers
引用
%master ALL= NOPASSWD: /usr/bin/getpubkey *

这个 getpubkey 是一个shell脚本,只包含一句 cat "/home/$user/.ssh/id_rsa.pub" ,用来获取某个用户的公钥

然后配合 authroize_user 这个脚本,将公钥发送到组长有权限访问的机器上,通过这种方式实现一个简陋的二级授权:
引用
sudo getpubkey $1 | ssh nginx@$host bash -c "cat /dev/stdin >> ~/.ssh/authorized_keys"


另外顺便提一下sudo的debug,似乎相关资料很少。在配置group的时候,直接去测试,有时会发现好像总是不对,但是sudo默认没有log,很难排查问题。实际上只要在 /etc/ 下面添加一个 sudo.conf 就好,文件内容为:
引用
Debug sudo /var/log/sudo_debug all@warn,plugin@info

再次执行sudo的时候就会看到debug log文件里的信息:
引用
Apr 22 14:12:03 sudo[21786] user_info: user=felix021
Apr 22 14:12:03 sudo[21786] user_info: pid=21786
Apr 22 14:12:03 sudo[21786] user_info: ppid=21785
Apr 22 14:12:03 sudo[21786] user_info: pgid=21785
Apr 22 14:12:03 sudo[21786] user_info: tcpgid=21785
Apr 22 14:12:03 sudo[21786] user_info: sid=21522
Apr 22 14:12:03 sudo[21786] user_info: uid=1002
Apr 22 14:12:03 sudo[21786] user_info: euid=0
Apr 22 14:12:03 sudo[21786] user_info: gid=1000
Apr 22 14:12:03 sudo[21786] user_info: egid=1000
Apr 22 14:12:03 sudo[21786] user_info: groups=1000

从这里可以看到,虽然前面把 felix021 添加到了 master 这个group下,但是sudo并没有识别出来。

放狗搜了一下才知道,原来linux下需要退出所有的登录session重新登录,才能生效。
]]>
0
<![CDATA[tc: 模拟网络异常的工具]]> http://www.udpwork.com/item/16221.html http://www.udpwork.com/item/16221.html#reviews Fri, 21 Apr 2017 15:59:11 +0800 鸟窝 http://www.udpwork.com/item/16221.html

Linux Traffic Control (tc)的扩展 Network Emulation (netem)可以很方便的模拟网络不好的情况,一般新的linux内核中(>= 2.6)已经内置了这个工具,可以方便的进行测试。

本文罗列了了tc的常用的模拟命令, 以备将来使用的时候查询。

主要参考了Linux基金会的官方介绍:netem

监控网卡。

首先要查看你的网卡信息,如:eth0,然后将这个网卡加入监控列表sudo tc qdisc add dev eth0 root netem。

如果不想再监控,可以移除这个网卡sudo tc qdisc del dev eth0 root netem

如果想查看监控列表, 可以使用tc -s qdisc。

qdisc是queueing discipline的缩写。

模拟网络延迟

固定延迟

1
tc qdisc add dev eth0 root netem delay 100ms

每个包都固定延迟100毫秒, 设置好后你可以使用ping命令测试。

固定延迟+小随机值

1
tc qdisc change dev eth0 root netem delay 100ms 10ms

延迟时间变成了100ms ± 10ms。

固定延迟+小随机值+相关系数

1
tc qdisc change dev eth0 root netem delay 100ms 10ms 25%

This causes the added delay to be 100ms ± 10ms with the next random element depending 25% on the last one. This isn't true statistical correlation, but an approximation.

遵循正态分布的延迟

典型情况下延迟并不是均分分布的,而是遵循类似正态分布的规律。所以你可以使用某种分布模拟延迟。

1
tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal

分布为normal、pareto、paretonormal等。

模拟丢包

随机丢弃一些包, 丢弃比率可以设置。丢失比最小为232 = 0.0000000232%。

1
tc qdisc change dev eth0 root netem loss 0.1%

上述命令会随机丢弃千分之一的包。

你还可以增加一个相关参数:

1
tc qdisc change dev eth0 root netem loss 0.3% 25%

丢弃率为千分之三, 后一个的丢弃的可能性和前一个的可能性的25%相关:

Probn= .25Probn-1+ .75Random

模拟包重复

1
tc qdisc change dev eth0 root netem duplicate 1%

类似丢包的命令,上面命令产生百分之一的重复包。

模拟错误包

模拟随机噪音(错误包), 这个功能在 2.6.16以及以后的版本中才加入。它会在包中随机位置更改一个bit。

1
tc qdisc change dev eth0 root netem corrupt 0.1%

模拟包乱序

1) 方式一
使用 gap。 第5th包(5、10、15、20)立即发送,其它的包会延迟10毫秒。

1
tc qdisc change dev eth0 root netem gap 5 delay 10ms

2) 方式二
方式一乱序方式是固定的,可以预测的。方式二引入随机性:

1
tc qdisc change dev eth0 root netem delay 10ms reorder 25% 50%

25%的包会立即发送, 其它的包会延迟10毫秒。相关系数为50%。

新版的netem的包延迟设置也可能导致包乱序,如果包延迟的有一定的随机性的话:

1
tc qdisc change dev eth0 root netem delay 100ms 75ms

因为延迟时间在100ms ± 75ms返回内, 就有可能第二包的延迟比第一个包的延迟小,先发出去。

控制包速(带宽)

没有直接命令,需要两条命令配合使用。

1234567
# tc qdisc add dev eth0 root handle 1:0 netem delay 100ms# tc qdisc add dev eth0 parent 1:1 handle 10: tbf rate 256kbit buffer 1600 limit 3000# tc -s qdisc ls dev eth0qdisc netem 1: limit 1000 delay 100.0ms Sent 0 bytes 0 pkts (dropped 0, overlimits 0 )qdisc tbf 10: rate 256Kbit burst 1599b lat 26.6ms Sent 0 bytes 0 pkts (dropped 0, overlimits 0 )
]]>

Linux Traffic Control (tc)的扩展 Network Emulation (netem)可以很方便的模拟网络不好的情况,一般新的linux内核中(>= 2.6)已经内置了这个工具,可以方便的进行测试。

本文罗列了了tc的常用的模拟命令, 以备将来使用的时候查询。

主要参考了Linux基金会的官方介绍:netem

监控网卡。

首先要查看你的网卡信息,如:eth0,然后将这个网卡加入监控列表sudo tc qdisc add dev eth0 root netem。

如果不想再监控,可以移除这个网卡sudo tc qdisc del dev eth0 root netem

如果想查看监控列表, 可以使用tc -s qdisc。

qdisc是queueing discipline的缩写。

模拟网络延迟

固定延迟

1
tc qdisc add dev eth0 root netem delay 100ms

每个包都固定延迟100毫秒, 设置好后你可以使用ping命令测试。

固定延迟+小随机值

1
tc qdisc change dev eth0 root netem delay 100ms 10ms

延迟时间变成了100ms ± 10ms。

固定延迟+小随机值+相关系数

1
tc qdisc change dev eth0 root netem delay 100ms 10ms 25%

This causes the added delay to be 100ms ± 10ms with the next random element depending 25% on the last one. This isn't true statistical correlation, but an approximation.

遵循正态分布的延迟

典型情况下延迟并不是均分分布的,而是遵循类似正态分布的规律。所以你可以使用某种分布模拟延迟。

1
tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal

分布为normal、pareto、paretonormal等。

模拟丢包

随机丢弃一些包, 丢弃比率可以设置。丢失比最小为232 = 0.0000000232%。

1
tc qdisc change dev eth0 root netem loss 0.1%

上述命令会随机丢弃千分之一的包。

你还可以增加一个相关参数:

1
tc qdisc change dev eth0 root netem loss 0.3% 25%

丢弃率为千分之三, 后一个的丢弃的可能性和前一个的可能性的25%相关:

Probn= .25Probn-1+ .75Random

模拟包重复

1
tc qdisc change dev eth0 root netem duplicate 1%

类似丢包的命令,上面命令产生百分之一的重复包。

模拟错误包

模拟随机噪音(错误包), 这个功能在 2.6.16以及以后的版本中才加入。它会在包中随机位置更改一个bit。

1
tc qdisc change dev eth0 root netem corrupt 0.1%

模拟包乱序

1) 方式一
使用 gap。 第5th包(5、10、15、20)立即发送,其它的包会延迟10毫秒。

1
tc qdisc change dev eth0 root netem gap 5 delay 10ms

2) 方式二
方式一乱序方式是固定的,可以预测的。方式二引入随机性:

1
tc qdisc change dev eth0 root netem delay 10ms reorder 25% 50%

25%的包会立即发送, 其它的包会延迟10毫秒。相关系数为50%。

新版的netem的包延迟设置也可能导致包乱序,如果包延迟的有一定的随机性的话:

1
tc qdisc change dev eth0 root netem delay 100ms 75ms

因为延迟时间在100ms ± 75ms返回内, 就有可能第二包的延迟比第一个包的延迟小,先发出去。

控制包速(带宽)

没有直接命令,需要两条命令配合使用。

1234567
# tc qdisc add dev eth0 root handle 1:0 netem delay 100ms# tc qdisc add dev eth0 parent 1:1 handle 10: tbf rate 256kbit buffer 1600 limit 3000# tc -s qdisc ls dev eth0qdisc netem 1: limit 1000 delay 100.0ms Sent 0 bytes 0 pkts (dropped 0, overlimits 0 )qdisc tbf 10: rate 256Kbit burst 1599b lat 26.6ms Sent 0 bytes 0 pkts (dropped 0, overlimits 0 )
]]>
0
<![CDATA[iOS的新变化 与 苹果微信的一次擦枪走火]]> http://www.udpwork.com/item/16220.html http://www.udpwork.com/item/16220.html#reviews Fri, 21 Apr 2017 14:42:42 +0800 魏武挥 http://www.udpwork.com/item/16220.html

美国有不少举世闻名的高科技公司,比如谷歌、苹果、亚马逊、Facebook,当然也包括感觉上似乎有些衰老的微软。

苹果在中国有相当大的一批中高端消费群体,口碑也一直非常好。

但其实对于这些公司,资本市场给出的评价是不一样的。

当下,谷歌的市盈率是32倍,Facebook的市盈率是55倍,老牌的微软的市盈率都有30倍。而亚马逊的市盈率高达224倍。

但苹果的市盈率只有区区17倍。

这其实是资本市场用金钱给苹果给出的一个定义:这是一家卖东西的高科技公司,但未必就是互联网公司。

乔布斯的所谓软硬合一,是业务逻辑,并不是资本逻辑,后者只看到了硬,并不在乎它的软。

 

资本市场对一家公司的判断,与其说是目前的盈利能力,不如说是看未来的能力盈利与目前的盈利能力之比。这个比越大,越说明这家公司增长可期,资本在当下进入越划算。

苹果的盈利能力非常强,现金储备也高到让人高山仰止的地步,但不到20倍的市盈率,说明资本市场认为,这家公司盈利的增长能力,需要打个问号。

制造业其实都有这样的特征,因为制造业每多卖一个设备,和互联网企业每多发展一个用户,它的成本是完全不同的,所能达到的所谓规模效应也是完全不同的。

这就是同样作为高科技公司,苹果可能不太被资本认定为互联网公司的原因。

一个消费产品的制造销售商,与一个互联网公司,它的核心区别就在于:你到底拥有的是消费者,还是用户?

 

资本市场的看法,也就是投资的角度而已,作为被投主体,可以很在乎,也可以很不在乎。以苹果现下的盈利能力、市场影响、现金储备,它并不一定需要太当回事。

但要说苹果完全不在乎,可能也未必。

一个有趣的事实是,苹果近一次的iOS更新,在“通用”这个模块里,用户这个概念被提升到史无前例的重要位置。

在过去,理论上讲,每一个苹果设备都应该对应着一个用户id,那就是Apple ID。但很多消费者变成苹果用户的路径是:利用这个Apple ID进行应用的下载和购买。

这两个动作,站在苹果角度,就叫应用的“分发和变现”。

苹果之所以对越狱非常恼火的原因就在于,一旦消费者使用越狱的机器,那么,ta就有很大的可能绕过苹果对应用“分发和变现”的控制而不再成为苹果的用户,这是它绝对不能容忍的。

丧失了这一点,苹果就真成一个造东西卖东西的纯制造业公司了。

 

苹果在用户体系的打造上,是用过蛮多方法的。它不仅仅希望消费者只是买它的设备,还希望消费者使用它的服务——这话的意思远远超越了用一个Apple ID去下载和购买应用。

它在内容上努过力,比如newsfeed这个东西,在地图上也努过力,甚至和谷歌还正式翻过脸,最近有迹象表明,它在面向个人使用的云服务iCloud,努力推进。

它的努力,至少在中国并不是太成功。

中国市场上,在阿里和腾讯的互相抗衡中,应用层面的发展极其迅速,而两家,都有自己的支付手段。

腾讯甚至拿出了小程序这样的东西,一时里被科技舆论视为至少是剿灭独立轻量级应用的另一个应用平台。

一个小细节是,一开始小程序的名字是“应用号”,显然犯了苹果的大忌,被迫改名。

而这样的称谓,也使得苹果本来就有些警惕的神经,再一次紧绷起来。

整套微信,再任由其发展下去,是不是就可以做到绕过苹果对“应用分发和变现”的控制?

 

微信这次对公号赞赏的调整,仅仅是苹果权衡之后,挑了较弱的一环下手。赞赏虽然对很多公号运营者(主要是自媒体人)的影响很大,但对腾讯本身的影响并不太大。

这只是一次小规模的“战斗”,腾讯也迅速妥协,撤下了赞赏。

但赞赏并不是今天才有的,而苹果选择这个时刻发难,我始终认为和最新版本iOS对用户体系的强化有关。

苹果也许可以不在乎资本市场从资本逻辑上对它的认定。

但苹果未必能容忍业务逻辑上真的成为了一家只有消费者没有用户的制造企业。

它自己在努力,也尽可能在断绝别的公司将它OTT化。

但在中国的苹果用户体系的难用,或许,真的会让它考虑,将一系列的网络服务,物理上放到中国来。

说到底,它并不是谷歌。

 

—— 首发 FT中文网 ——

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

iOS的新变化 与 苹果微信的一次擦枪走火,首发于扯氮集

]]>

美国有不少举世闻名的高科技公司,比如谷歌、苹果、亚马逊、Facebook,当然也包括感觉上似乎有些衰老的微软。

苹果在中国有相当大的一批中高端消费群体,口碑也一直非常好。

但其实对于这些公司,资本市场给出的评价是不一样的。

当下,谷歌的市盈率是32倍,Facebook的市盈率是55倍,老牌的微软的市盈率都有30倍。而亚马逊的市盈率高达224倍。

但苹果的市盈率只有区区17倍。

这其实是资本市场用金钱给苹果给出的一个定义:这是一家卖东西的高科技公司,但未必就是互联网公司。

乔布斯的所谓软硬合一,是业务逻辑,并不是资本逻辑,后者只看到了硬,并不在乎它的软。

 

资本市场对一家公司的判断,与其说是目前的盈利能力,不如说是看未来的能力盈利与目前的盈利能力之比。这个比越大,越说明这家公司增长可期,资本在当下进入越划算。

苹果的盈利能力非常强,现金储备也高到让人高山仰止的地步,但不到20倍的市盈率,说明资本市场认为,这家公司盈利的增长能力,需要打个问号。

制造业其实都有这样的特征,因为制造业每多卖一个设备,和互联网企业每多发展一个用户,它的成本是完全不同的,所能达到的所谓规模效应也是完全不同的。

这就是同样作为高科技公司,苹果可能不太被资本认定为互联网公司的原因。

一个消费产品的制造销售商,与一个互联网公司,它的核心区别就在于:你到底拥有的是消费者,还是用户?

 

资本市场的看法,也就是投资的角度而已,作为被投主体,可以很在乎,也可以很不在乎。以苹果现下的盈利能力、市场影响、现金储备,它并不一定需要太当回事。

但要说苹果完全不在乎,可能也未必。

一个有趣的事实是,苹果近一次的iOS更新,在“通用”这个模块里,用户这个概念被提升到史无前例的重要位置。

在过去,理论上讲,每一个苹果设备都应该对应着一个用户id,那就是Apple ID。但很多消费者变成苹果用户的路径是:利用这个Apple ID进行应用的下载和购买。

这两个动作,站在苹果角度,就叫应用的“分发和变现”。

苹果之所以对越狱非常恼火的原因就在于,一旦消费者使用越狱的机器,那么,ta就有很大的可能绕过苹果对应用“分发和变现”的控制而不再成为苹果的用户,这是它绝对不能容忍的。

丧失了这一点,苹果就真成一个造东西卖东西的纯制造业公司了。

 

苹果在用户体系的打造上,是用过蛮多方法的。它不仅仅希望消费者只是买它的设备,还希望消费者使用它的服务——这话的意思远远超越了用一个Apple ID去下载和购买应用。

它在内容上努过力,比如newsfeed这个东西,在地图上也努过力,甚至和谷歌还正式翻过脸,最近有迹象表明,它在面向个人使用的云服务iCloud,努力推进。

它的努力,至少在中国并不是太成功。

中国市场上,在阿里和腾讯的互相抗衡中,应用层面的发展极其迅速,而两家,都有自己的支付手段。

腾讯甚至拿出了小程序这样的东西,一时里被科技舆论视为至少是剿灭独立轻量级应用的另一个应用平台。

一个小细节是,一开始小程序的名字是“应用号”,显然犯了苹果的大忌,被迫改名。

而这样的称谓,也使得苹果本来就有些警惕的神经,再一次紧绷起来。

整套微信,再任由其发展下去,是不是就可以做到绕过苹果对“应用分发和变现”的控制?

 

微信这次对公号赞赏的调整,仅仅是苹果权衡之后,挑了较弱的一环下手。赞赏虽然对很多公号运营者(主要是自媒体人)的影响很大,但对腾讯本身的影响并不太大。

这只是一次小规模的“战斗”,腾讯也迅速妥协,撤下了赞赏。

但赞赏并不是今天才有的,而苹果选择这个时刻发难,我始终认为和最新版本iOS对用户体系的强化有关。

苹果也许可以不在乎资本市场从资本逻辑上对它的认定。

但苹果未必能容忍业务逻辑上真的成为了一家只有消费者没有用户的制造企业。

它自己在努力,也尽可能在断绝别的公司将它OTT化。

但在中国的苹果用户体系的难用,或许,真的会让它考虑,将一系列的网络服务,物理上放到中国来。

说到底,它并不是谷歌。

 

—— 首发 FT中文网 ——

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

iOS的新变化 与 苹果微信的一次擦枪走火,首发于扯氮集

]]>
0
<![CDATA[MMORPG 客户端的网络消息框架]]> http://www.udpwork.com/item/16219.html http://www.udpwork.com/item/16219.html#reviews Fri, 21 Apr 2017 12:33:25 +0800 云风 http://www.udpwork.com/item/16219.html 昨天和人闲扯,谈到了 MMORPG 客户端的网络消息应该基于怎样的模型。依稀记得很早写过我的观点,但是 blog 上却找不到。那么今天补上这么一篇吧。

我认为,MMO 类游戏,服务器扮演的角色是虚拟的世界,一切的状态变化都是在游戏服务器仲裁和演化的。而客户端的角色本质上是一个状态呈现器,把玩家视角看到的虚拟世界的状态,通过网络消息呈现出来。所以、在设计客户端的网络消息分发框架时,应围绕这个职责来设计。

客户端发起的请求分两种:一种是通知服务器,我扮演的角色状态发生了改变,请求服务器仲裁;另一种是,我期望获取服务器对象的最新状态。后一种有时以服务器主动推送来解决,也可以用主动请求,两者主要的区别在于流量控制。

其实、对于客户端作为状态呈现这个角色而言,请求和回应之间的关系并没有什么紧密联系,并不适合使用基于 RPC 的网络模型。这个无关乎 RPC 的实现是用 callback 还是用 coroutine 。

这是因为,当服务器的状态改变时,客户端关心的应该是这类事情发生时,我应该如何呈现;而不是具体到某个请求发出后,收到回应我应该怎么处理。RPC 把请求和回应紧密耦合在一起,让回应的处理流程强依赖请求时的上下文,这样容易引起不必要的状态管理。

比如,我看到我们公司的一个项目中曾经出过这样的 bug :UI 界面上点击一个按钮,用 callback 的形式发起了一次 RPC 调用;在回应的 callback 函数中对 UI 界面上的元素做了一系列的修改。可是在回应的网络消息收到时, UI 界面已经关闭了,由于处于节约内存的考虑,还触发了一些对象销毁流程,结果因为操控不存在的对象造成了 bug 。

你可以从别的角度来看待这个 bug 。比如说应该建立一个更稳固的 UI 框架,做更严谨的生命期管理。但我认为,根源问题就是没有把请求和回应解耦造成的。

我们应该这样看待按钮点击事件:它只是触发了一个事件在服务器上运行,这个事件导致了某个服务器上的对象状态改变了,而回应包只是通知了状态改变的结果。客户端真正要做的是怎样正确呈现这个结果。

如果我们把客户端处理网络包的流程都归纳成类似的模型,统一成一个简单的框架就很容易了。

客户端根据收到的网络包进行相应的处理,无论这个网络包是服务器的推送、还是对之前客户端发起请求的回应。在这个框架下,只关心来了一个网络包后的类别,根据这个类别来决定要做哪类事情。处理流程是关联在消息类别上的,而不是关联在单个请求上的。

对于请求回应的处理,其实和推送的处理并没有本质的不同。只是实现上略有差别。对应回应包的处理,处理流程得到的参数不仅仅是网络包,还需要有对应的请求数据(也就是客户端当初自己发起请求的参数)和这个事件绑定的客户端本地对象。

通常我们会用一个 session 号来关联回应包和请求包,在发起请求时,不仅把请求内容打包发给服务器,同时还把它记录在本地,用 session 关联起来;这样在收到请求时,可以根据 session 找回当初请求的参数,以及请求的类型,这样就可以不必让服务器推送完整的状态值,客户端可以自己找到匹配的内容。

在大部分情况下,我们还需要在发起请求时,给这个 session 绑定一个本地的对象。虽然请求本身肯定有这个信息(否则服务器就不知道该请求到底操作呢什么东西),但额外的一个本地对象使用起来更方便,可以用来携带少量本地状态信息。在回应抵达时,直接操作这个绑定对象写起来更方便。

如果用 lua 来实现,看起来大致是这样的:

send_request(obj, req_type, req_data)-- 发起一个请求,请求类型为 req type ,参数是 req data ,绑定对象为 obj 。

function net.req_type(obj, req_data, resp_data)-- 定义一个函数来处理 req_type 这种类型的消息,可以获得发起请求时的 obj 和 req data 以及服务器的回应 resp data 。

]]>
昨天和人闲扯,谈到了 MMORPG 客户端的网络消息应该基于怎样的模型。依稀记得很早写过我的观点,但是 blog 上却找不到。那么今天补上这么一篇吧。

我认为,MMO 类游戏,服务器扮演的角色是虚拟的世界,一切的状态变化都是在游戏服务器仲裁和演化的。而客户端的角色本质上是一个状态呈现器,把玩家视角看到的虚拟世界的状态,通过网络消息呈现出来。所以、在设计客户端的网络消息分发框架时,应围绕这个职责来设计。

客户端发起的请求分两种:一种是通知服务器,我扮演的角色状态发生了改变,请求服务器仲裁;另一种是,我期望获取服务器对象的最新状态。后一种有时以服务器主动推送来解决,也可以用主动请求,两者主要的区别在于流量控制。

其实、对于客户端作为状态呈现这个角色而言,请求和回应之间的关系并没有什么紧密联系,并不适合使用基于 RPC 的网络模型。这个无关乎 RPC 的实现是用 callback 还是用 coroutine 。

这是因为,当服务器的状态改变时,客户端关心的应该是这类事情发生时,我应该如何呈现;而不是具体到某个请求发出后,收到回应我应该怎么处理。RPC 把请求和回应紧密耦合在一起,让回应的处理流程强依赖请求时的上下文,这样容易引起不必要的状态管理。

比如,我看到我们公司的一个项目中曾经出过这样的 bug :UI 界面上点击一个按钮,用 callback 的形式发起了一次 RPC 调用;在回应的 callback 函数中对 UI 界面上的元素做了一系列的修改。可是在回应的网络消息收到时, UI 界面已经关闭了,由于处于节约内存的考虑,还触发了一些对象销毁流程,结果因为操控不存在的对象造成了 bug 。

你可以从别的角度来看待这个 bug 。比如说应该建立一个更稳固的 UI 框架,做更严谨的生命期管理。但我认为,根源问题就是没有把请求和回应解耦造成的。

我们应该这样看待按钮点击事件:它只是触发了一个事件在服务器上运行,这个事件导致了某个服务器上的对象状态改变了,而回应包只是通知了状态改变的结果。客户端真正要做的是怎样正确呈现这个结果。

如果我们把客户端处理网络包的流程都归纳成类似的模型,统一成一个简单的框架就很容易了。

客户端根据收到的网络包进行相应的处理,无论这个网络包是服务器的推送、还是对之前客户端发起请求的回应。在这个框架下,只关心来了一个网络包后的类别,根据这个类别来决定要做哪类事情。处理流程是关联在消息类别上的,而不是关联在单个请求上的。

对于请求回应的处理,其实和推送的处理并没有本质的不同。只是实现上略有差别。对应回应包的处理,处理流程得到的参数不仅仅是网络包,还需要有对应的请求数据(也就是客户端当初自己发起请求的参数)和这个事件绑定的客户端本地对象。

通常我们会用一个 session 号来关联回应包和请求包,在发起请求时,不仅把请求内容打包发给服务器,同时还把它记录在本地,用 session 关联起来;这样在收到请求时,可以根据 session 找回当初请求的参数,以及请求的类型,这样就可以不必让服务器推送完整的状态值,客户端可以自己找到匹配的内容。

在大部分情况下,我们还需要在发起请求时,给这个 session 绑定一个本地的对象。虽然请求本身肯定有这个信息(否则服务器就不知道该请求到底操作呢什么东西),但额外的一个本地对象使用起来更方便,可以用来携带少量本地状态信息。在回应抵达时,直接操作这个绑定对象写起来更方便。

如果用 lua 来实现,看起来大致是这样的:

send_request(obj, req_type, req_data)-- 发起一个请求,请求类型为 req type ,参数是 req data ,绑定对象为 obj 。

function net.req_type(obj, req_data, resp_data)-- 定义一个函数来处理 req_type 这种类型的消息,可以获得发起请求时的 obj 和 req data 以及服务器的回应 resp data 。

]]>
0
<![CDATA[Go HTTP Redirect的知识点总结]]> http://www.udpwork.com/item/16218.html http://www.udpwork.com/item/16218.html#reviews Thu, 20 Apr 2017 14:10:21 +0800 鸟窝 http://www.udpwork.com/item/16218.html HTTP 规范中定义了返回码为3xx代表客户端需要做一些额外的工作来完成请求,大部分3xx用来做转发(redirect)。

状态码的详细说明可以参照规范或者wikipedia维基百科, 以下是代码的简短介绍。

  • 300 Multiple Choices: 返回多个可供选择的资源
  • 301 Moved Permanently: 请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一
  • 302 Found: 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求,HTTP 1.0中的意义是Moved Temporarily,但是很多浏览器的实现是按照303的处实现的,所以HTTP 1.1中增加了 303和307的状态码来区分不同的行为
  • 303 See Other (since HTTP/1.1): 对应当前请求的响应可以在另一个URI上被找到,而且客户端应当采用GET的方式访问那个资源
  • 304 Not Modified (RFC 7232): 请求的资源没有改变
  • 305 Use Proxy (since HTTP/1.1): 被请求的资源必须通过指定的代理才能被访问
  • 306 Switch Proxy: 在最新版的规范中,306状态码已经不再被使用
  • 307 Temporary Redirect (since HTTP/1.1): 请求的资源现在临时从不同的URI响应请求,和303不同,它还是使用原先的Method
  • 308 Permanent Redirect (RFC 7538): 请求的资源已永久移动到新位置,并且新请求的Method不能改变

Go 的 http 库在实现的过程中也在不断的完成和修改其中的Bug,在 1.8版本中解决了前面版本中实现的问题 (你可以在 Go issues中搜索 redirect 来查看相关的issue)。 本文梳理了 Go 中 Redirect 的相关知识,以便你在遇到转发的问题时心中有数。

转发策略和默认转发次数。

http.Client包含一个CheckRedirect字段,用来定义转发的策略,如果你没有设置,则默认使用defaultCheckRedirect:

1234567
func (c *Client) checkRedirect(req *Request, via []*Request) error {	fn := c.CheckRedirect	if fn == nil {		fn = defaultCheckRedirect	}	return fn(req, via)}

这个函数会在执行转发之前被调用,可以看到这个函数如果返回err,则不再进行转发了。
这个函数的第一个参数req是即将转发使用的request,第二个参数via已经请求的requests。

12345678910111213141516171819202122
for {    ……    //需要转发    {        ……        err = c.checkRedirect(req, reqs)		if err == ErrUseLastResponse {			return resp, nil		}		//discard previous response        ……		if err != nil {			ue := uerr(err)			ue.(*url.Error).URL = loc			return resp, ue		}    }    ……}

默认的转发策略是最多10次转发, 避免转发次数过高或者死循环。

123456
func defaultCheckRedirect(req *Request, via []*Request) error {	if len(via) >= 10 {		return errors.New("stopped after 10 redirects")	}	return nil}

如果你要实现不同的转发策略,你需要定义自己的CheckRedirect。

转发安全

1)当转发的request中包含安全的信息Header时, 比如Authorization、WWW-Authenticate、Cookie等Header,如果是跨域,则这些头部不会被复制到新的请求中。

123456789
func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool {	switch CanonicalHeaderKey(headerKey) {	case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":		ihost := strings.ToLower(initial.Host)		dhost := strings.ToLower(dest.Host)		return isDomainOrSubdomain(dhost, ihost)	}	return true}

2)如果设置了一个非空的 cookie Jar, 转发响应会修改 cookie jar中的值,但是下一次转发的时候,Cookieheader会被处理,忽略那些变动的cookie.

when forwarding the "Cookie" header with a non-nil cookie Jar.
Since each redirect may mutate the state of the cookie jar,
a redirect may possibly alter a cookie set in the initial request.
When forwarding the "Cookie" header, any mutated cookies will be omitted,
with the expectation that the Jar will insert those mutated cookies
with the updated values (assuming the origin matches).
If Jar is nil, the initial cookies are forwarded without change.

具体的你可以查看makeHeadersCopier的实现。
可以看到每次redirect会删除上次的Redirect造成的变动,再恢复原始的请求的Coookie。

1234567891011121314151617181920212223242526272829
  if c.Jar != nil && icookies != nil {	var changed bool	resp := req.Response // The response that caused the upcoming redirect	for _, c := range resp.Cookies() {		if _, ok := icookies[c.Name]; ok {			delete(icookies, c.Name)			changed = true		}	}	if changed {		ireqhdr.Del("Cookie")		var ss []string		for _, cs := range icookies {			for _, c := range cs {				ss = append(ss, c.Name+"="+c.Value)			}		}		sort.Strings(ss) // Ensure deterministic headers		ireqhdr.Set("Cookie", strings.Join(ss, "; "))	}}// Copy the initial request's Header values// (at least the safe ones).for k, vv := range ireqhdr {	if shouldCopyHeaderOnRedirect(k, preq.URL, req.URL) {		req.Header[k] = vv	}}

转发规则

当服务器返回一个转发response的时候, client首先使用CheckRedirect函数检查是否要进行转发。

默认会处理下列请求:

  • 301 (Moved Permanently)
  • 302 (Found)
  • 303 (See Other)
  • 307 (Temporary Redirect)
  • 308 (Permanent Redirect)

如果需要转发, 对于301、302、303的状态码, 接下来转发的请求会将请求Method 转换成GETmethod (如果原始请求Method是HEAD则不变,还是HEAD), 而且body为空, 尽管原始的请求可能包含body。 对于307、308状态码,接下来转发的请求的Method 没有变化,和原始的请求保持一致, 并且还是使用原始的body内容来发送转发请求。

代码处理的逻辑是由redirectBehavior函数实现的。

1234567891011121314151617181920212223242526
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {	switch resp.StatusCode {	case 301, 302, 303:		redirectMethod = reqMethod		shouldRedirect = true		includeBody = false		if reqMethod != "GET" && reqMethod != "HEAD" {			redirectMethod = "GET"		}	case 307, 308:		redirectMethod = reqMethod		shouldRedirect = true		includeBody = true			if resp.Header.Get("Location") == "" {			shouldRedirect = false			break		}		if ireq.GetBody == nil && ireq.outgoingLength() != 0 {			shouldRedirect = false		}	}	return redirectMethod, shouldRedirect, includeBody}

以上是介绍的http Redirect相关的内容,它们主要是客户端的代码逻辑。 下面两节介绍一下与http redirect有一点点关系的内容。

RedirectHandler

http定义了一个便利类型:RedirectHandler, 它是一个预定义的http handler,可以将指定的请求转发到指定的url中, 主要就是使用设定的 url 和 status code 设置 response 的 Location。

它用作服务器端的开发中。

1
func RedirectHandler(url string, code int) Handler

注意 code应该是3xx的状态码, 通常是StatusMovedPermanently,StatusFound或者StatusSeeOther。

RoundTripper

RoundTripper也用作客户端的开发。

RoundTripper代表一次http 的请求。 当使用redirect的时候,你可能redirect多次,也就是执行了n次的http请求。

123
type RoundTripper interface {        RoundTrip(*Request) (*Response, error)}

DefaultTransport是默认RoundTripper的实现。它建立网络连接,并且会缓存以便后续的请求重用。它会使用$HTTP_PROXY和$NO_PROXY(或者 $http_proxy, $no_proxy)环境变量来设置代理.

其它的RoundTripper的实现还有NewFileTransport创建的RoundTripper,用来服务文件系统,将文件系统映射成http的处理方式。

123456789101112
var DefaultTransport RoundTripper = &Transport{        Proxy: ProxyFromEnvironment,        DialContext: (&net.Dialer{                Timeout:   30 * time.Second,                KeepAlive: 30 * time.Second,                DualStack: true,        }).DialContext,        MaxIdleConns:          100,        IdleConnTimeout:       90 * time.Second,        TLSHandshakeTimeout:   10 * time.Second,        ExpectContinueTimeout: 1 * time.Second,}

当Redirect的时候,默认转发是http库自动帮你实现的,如果你想对于转发过程做深入的跟踪的话(简单跟踪可以使用httptrace),你可以自定义一个RoundTripper,比如下面的这个:

http://stackoverflow.com/questions/24577494/how-to-get-the-http-redirect-status-codes-in-golang
12345678910111213141516171819
type LogRedirects struct {    Transport http.RoundTripper}func (l LogRedirects) RoundTrip(req *http.Request) (resp *http.Response, err error) {    t := l.Transport    if t == nil {        t = http.DefaultTransport    }    resp, err = t.RoundTrip(req)    if err != nil {        return    }    switch resp.StatusCode {    case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect:        log.Println("Request for", req.URL, "redirected with status", resp.StatusCode)    }    return}

创建Client的时候可以使用下面的代码:

1
client := &http.Client{Transport: LogRedirects{}}

这样每次转发过程我们都可以追踪。

]]>
HTTP 规范中定义了返回码为3xx代表客户端需要做一些额外的工作来完成请求,大部分3xx用来做转发(redirect)。

状态码的详细说明可以参照规范或者wikipedia维基百科, 以下是代码的简短介绍。

  • 300 Multiple Choices: 返回多个可供选择的资源
  • 301 Moved Permanently: 请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一
  • 302 Found: 请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求,HTTP 1.0中的意义是Moved Temporarily,但是很多浏览器的实现是按照303的处实现的,所以HTTP 1.1中增加了 303和307的状态码来区分不同的行为
  • 303 See Other (since HTTP/1.1): 对应当前请求的响应可以在另一个URI上被找到,而且客户端应当采用GET的方式访问那个资源
  • 304 Not Modified (RFC 7232): 请求的资源没有改变
  • 305 Use Proxy (since HTTP/1.1): 被请求的资源必须通过指定的代理才能被访问
  • 306 Switch Proxy: 在最新版的规范中,306状态码已经不再被使用
  • 307 Temporary Redirect (since HTTP/1.1): 请求的资源现在临时从不同的URI响应请求,和303不同,它还是使用原先的Method
  • 308 Permanent Redirect (RFC 7538): 请求的资源已永久移动到新位置,并且新请求的Method不能改变

Go 的 http 库在实现的过程中也在不断的完成和修改其中的Bug,在 1.8版本中解决了前面版本中实现的问题 (你可以在 Go issues中搜索 redirect 来查看相关的issue)。 本文梳理了 Go 中 Redirect 的相关知识,以便你在遇到转发的问题时心中有数。

转发策略和默认转发次数。

http.Client包含一个CheckRedirect字段,用来定义转发的策略,如果你没有设置,则默认使用defaultCheckRedirect:

1234567
func (c *Client) checkRedirect(req *Request, via []*Request) error {	fn := c.CheckRedirect	if fn == nil {		fn = defaultCheckRedirect	}	return fn(req, via)}

这个函数会在执行转发之前被调用,可以看到这个函数如果返回err,则不再进行转发了。
这个函数的第一个参数req是即将转发使用的request,第二个参数via已经请求的requests。

12345678910111213141516171819202122
for {    ……    //需要转发    {        ……        err = c.checkRedirect(req, reqs)		if err == ErrUseLastResponse {			return resp, nil		}		//discard previous response        ……		if err != nil {			ue := uerr(err)			ue.(*url.Error).URL = loc			return resp, ue		}    }    ……}

默认的转发策略是最多10次转发, 避免转发次数过高或者死循环。

123456
func defaultCheckRedirect(req *Request, via []*Request) error {	if len(via) >= 10 {		return errors.New("stopped after 10 redirects")	}	return nil}

如果你要实现不同的转发策略,你需要定义自己的CheckRedirect。

转发安全

1)当转发的request中包含安全的信息Header时, 比如Authorization、WWW-Authenticate、Cookie等Header,如果是跨域,则这些头部不会被复制到新的请求中。

123456789
func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool {	switch CanonicalHeaderKey(headerKey) {	case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":		ihost := strings.ToLower(initial.Host)		dhost := strings.ToLower(dest.Host)		return isDomainOrSubdomain(dhost, ihost)	}	return true}

2)如果设置了一个非空的 cookie Jar, 转发响应会修改 cookie jar中的值,但是下一次转发的时候,Cookieheader会被处理,忽略那些变动的cookie.

when forwarding the "Cookie" header with a non-nil cookie Jar.
Since each redirect may mutate the state of the cookie jar,
a redirect may possibly alter a cookie set in the initial request.
When forwarding the "Cookie" header, any mutated cookies will be omitted,
with the expectation that the Jar will insert those mutated cookies
with the updated values (assuming the origin matches).
If Jar is nil, the initial cookies are forwarded without change.

具体的你可以查看makeHeadersCopier的实现。
可以看到每次redirect会删除上次的Redirect造成的变动,再恢复原始的请求的Coookie。

1234567891011121314151617181920212223242526272829
  if c.Jar != nil && icookies != nil {	var changed bool	resp := req.Response // The response that caused the upcoming redirect	for _, c := range resp.Cookies() {		if _, ok := icookies[c.Name]; ok {			delete(icookies, c.Name)			changed = true		}	}	if changed {		ireqhdr.Del("Cookie")		var ss []string		for _, cs := range icookies {			for _, c := range cs {				ss = append(ss, c.Name+"="+c.Value)			}		}		sort.Strings(ss) // Ensure deterministic headers		ireqhdr.Set("Cookie", strings.Join(ss, "; "))	}}// Copy the initial request's Header values// (at least the safe ones).for k, vv := range ireqhdr {	if shouldCopyHeaderOnRedirect(k, preq.URL, req.URL) {		req.Header[k] = vv	}}

转发规则

当服务器返回一个转发response的时候, client首先使用CheckRedirect函数检查是否要进行转发。

默认会处理下列请求:

  • 301 (Moved Permanently)
  • 302 (Found)
  • 303 (See Other)
  • 307 (Temporary Redirect)
  • 308 (Permanent Redirect)

如果需要转发, 对于301、302、303的状态码, 接下来转发的请求会将请求Method 转换成GETmethod (如果原始请求Method是HEAD则不变,还是HEAD), 而且body为空, 尽管原始的请求可能包含body。 对于307、308状态码,接下来转发的请求的Method 没有变化,和原始的请求保持一致, 并且还是使用原始的body内容来发送转发请求。

代码处理的逻辑是由redirectBehavior函数实现的。

1234567891011121314151617181920212223242526
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {	switch resp.StatusCode {	case 301, 302, 303:		redirectMethod = reqMethod		shouldRedirect = true		includeBody = false		if reqMethod != "GET" && reqMethod != "HEAD" {			redirectMethod = "GET"		}	case 307, 308:		redirectMethod = reqMethod		shouldRedirect = true		includeBody = true			if resp.Header.Get("Location") == "" {			shouldRedirect = false			break		}		if ireq.GetBody == nil && ireq.outgoingLength() != 0 {			shouldRedirect = false		}	}	return redirectMethod, shouldRedirect, includeBody}

以上是介绍的http Redirect相关的内容,它们主要是客户端的代码逻辑。 下面两节介绍一下与http redirect有一点点关系的内容。

RedirectHandler

http定义了一个便利类型:RedirectHandler, 它是一个预定义的http handler,可以将指定的请求转发到指定的url中, 主要就是使用设定的 url 和 status code 设置 response 的 Location。

它用作服务器端的开发中。

1
func RedirectHandler(url string, code int) Handler

注意 code应该是3xx的状态码, 通常是StatusMovedPermanently,StatusFound或者StatusSeeOther。

RoundTripper

RoundTripper也用作客户端的开发。

RoundTripper代表一次http 的请求。 当使用redirect的时候,你可能redirect多次,也就是执行了n次的http请求。

123
type RoundTripper interface {        RoundTrip(*Request) (*Response, error)}

DefaultTransport是默认RoundTripper的实现。它建立网络连接,并且会缓存以便后续的请求重用。它会使用$HTTP_PROXY和$NO_PROXY(或者 $http_proxy, $no_proxy)环境变量来设置代理.

其它的RoundTripper的实现还有NewFileTransport创建的RoundTripper,用来服务文件系统,将文件系统映射成http的处理方式。

123456789101112
var DefaultTransport RoundTripper = &Transport{        Proxy: ProxyFromEnvironment,        DialContext: (&net.Dialer{                Timeout:   30 * time.Second,                KeepAlive: 30 * time.Second,                DualStack: true,        }).DialContext,        MaxIdleConns:          100,        IdleConnTimeout:       90 * time.Second,        TLSHandshakeTimeout:   10 * time.Second,        ExpectContinueTimeout: 1 * time.Second,}

当Redirect的时候,默认转发是http库自动帮你实现的,如果你想对于转发过程做深入的跟踪的话(简单跟踪可以使用httptrace),你可以自定义一个RoundTripper,比如下面的这个:

http://stackoverflow.com/questions/24577494/how-to-get-the-http-redirect-status-codes-in-golang
12345678910111213141516171819
type LogRedirects struct {    Transport http.RoundTripper}func (l LogRedirects) RoundTrip(req *http.Request) (resp *http.Response, err error) {    t := l.Transport    if t == nil {        t = http.DefaultTransport    }    resp, err = t.RoundTrip(req)    if err != nil {        return    }    switch resp.StatusCode {    case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect:        log.Println("Request for", req.URL, "redirected with status", resp.StatusCode)    }    return}

创建Client的时候可以使用下面的代码:

1
client := &http.Client{Transport: LogRedirects{}}

这样每次转发过程我们都可以追踪。

]]>
0
<![CDATA[易到的问题是乐视能不拖的么?]]> http://www.udpwork.com/item/16217.html http://www.udpwork.com/item/16217.html#reviews Tue, 18 Apr 2017 10:18:32 +0800 魏武挥 http://www.udpwork.com/item/16217.html

我的朋友三表同学新发了一篇文章:《乐视活生生把易到拖成了政治问题》。

在一个深刻检讨群里,我表示:不以为然。

虽然三表同学是一个铁杆乐视黑,但我还是觉得,三表此文有替乐视开脱之嫌。

这不是拖不拖的问题。

拖,说明主观能动性的高低,对事情的结果有影响作用。

但易到的问题,绝不是乐视的主观能动性。

说的直白一点,

这是一种必然。

 

很多很多年前,我在一家证券公司上班。这个公司的名字叫:富友证券。

这是一家很小的小型券商,全国也就6个营业部,和当年什么一百单八将的申万(就是有108个营业部),完全没的比。

但富友证券敢打价格战,以至于客户不少,尤其是大散。上海大本营的这个营业部,曾经创下顶其它券商六个营业部的交易总额的辉煌战绩。

富友证券后来被彼时上海首富周正毅收购。

我那时比较年轻,不太理解周正毅的行为。

因为作为这家券商的一个中层管理人员,知道富友证券经纪业务虽然搞得风生水起,但其实是赔本赚吆喝,压根不挣钱。

如果不是连一张a4纸都要算计成本,这个公司必定亏损,周正毅要这么个货干嘛呢?

周正毅派了他的舅子过来做董事长。这位董事长也不太关心具体业务,甚至不太来上班。只是有一条:你们得自己养活自己,想要集团贴钱,不可能。

后来我慢慢明白了,对于周正毅来说,股民的保证金才是真正的金矿。

那年头证券行业没像今天那样管得严,控制人挪用保证金是常有的事。

对于周氏而言,这笔保证金,就是周家集团最重要的资金来源之一,而且还是不要利息的。

再后来,周氏牵扯到上海地区一个震动全国的贪腐案中,整个集团随之垮台,这是后话。

但富友证券这段经历,让年纪轻轻的我学习到一件事:如何获得廉价资金,是企业运营的重中之重。

不过,这是走钢丝的活,稍有不慎,粉身碎骨。

 

最近热播的《人民的名义》,我倒是特别同情那位老板蔡成功。

这个角色反映出两点:1、搞资金风险巨大;2、底层的民营企业老板,活得极其不容易。

蔡成功在煤矿这件事上赔了血本,最终引发银行断贷、高利贷逼债,窟窿越搞越大,有大几亿之多。

没辙,只有一条路还能保命:坐监去算求。

很多老板想辙弄不要利息的资金,前文说的周正毅是一例。

后来证券行业明令券商不得挪用保证金,慢慢这种不要利息的资金,是没有了。

而今天,易到和乐视又是一例。

但这里不是不要利息,而是利息极高。

乐视对资金的饥渴,使得它明知鸠有毒,却不喝不行。

 

周航在易到内部被架空,不是什么新闻。乐视进入易到后,周航就基本靠边站了。

乐视对易到的看重,我和一位朋友分析过,什么智能汽车搞出来给易到就生态化反了,纯属梦呓。核心利益就一点:这里是一个庞大的资金入口。

用充返活动,吸引用户存入资金,从而获得巨额现金。

到底易到不是金融企业,这种钱,并不在一行三会的视野之内,也不存在什么禁止资金池这一说。

这一大笔款项入账,用于什么呢?

买余额宝,你信么?

但这钱,真有毒。

“利息”极高,用户存一百当一百五、一百七甚至两百用,你说代价大不大。

乐视自己都患了资金饥渴症晚期,指望乐视去贴这个窟窿,怎么可能!

 

所以,这不是拖不拖的问题。

而是两个字:没辙。

我以前说过,乐视是不是庞氏骗局,还有待考证。

但易到这个窟窿,很明显就有庞氏的特点。

张三充了一百,得两百,在那里美滋滋使用,而这个一百,便部分去还了司机李四的账,部分,估计填其它窟窿去了。

这个盘子转啊转啊转啊,终于有一天。眼瞅着就快赚不下去了。

要命的是,充返活动吧,搞到企业报表上,那就是巨额的负债。

如果仅考虑商业,真是没什么大佬敢接了。

 

我身边有几个朋友炒乐视股票,还有一位下注还不小。

小四那部什么CG电影大败亏输,我和其中一位说,还有乐视股票么?赶紧抛了吧。

因为,乐视影业,也是整个乐视大厦的现金流业务。

乐视搞什么硬件免费,充n年的会员费,都是现金业务。

但天底下,真没什么资金,是不需要支付代价的。

低估了这份代价,就会变成蔡成功。

只是,蔡老板真不是野心催的。

有些人,则是自作孽啊!

 

以下文字,不玩阴阳师的看不懂。

我把座敷童子命名为:易到。

 

—— 首发 扯氮集 ——

版权声明  及  商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

易到的问题是乐视能不拖的么?,首发于扯氮集

]]>

我的朋友三表同学新发了一篇文章:《乐视活生生把易到拖成了政治问题》。

在一个深刻检讨群里,我表示:不以为然。

虽然三表同学是一个铁杆乐视黑,但我还是觉得,三表此文有替乐视开脱之嫌。

这不是拖不拖的问题。

拖,说明主观能动性的高低,对事情的结果有影响作用。

但易到的问题,绝不是乐视的主观能动性。

说的直白一点,

这是一种必然。

 

很多很多年前,我在一家证券公司上班。这个公司的名字叫:富友证券。

这是一家很小的小型券商,全国也就6个营业部,和当年什么一百单八将的申万(就是有108个营业部),完全没的比。

但富友证券敢打价格战,以至于客户不少,尤其是大散。上海大本营的这个营业部,曾经创下顶其它券商六个营业部的交易总额的辉煌战绩。

富友证券后来被彼时上海首富周正毅收购。

我那时比较年轻,不太理解周正毅的行为。

因为作为这家券商的一个中层管理人员,知道富友证券经纪业务虽然搞得风生水起,但其实是赔本赚吆喝,压根不挣钱。

如果不是连一张a4纸都要算计成本,这个公司必定亏损,周正毅要这么个货干嘛呢?

周正毅派了他的舅子过来做董事长。这位董事长也不太关心具体业务,甚至不太来上班。只是有一条:你们得自己养活自己,想要集团贴钱,不可能。

后来我慢慢明白了,对于周正毅来说,股民的保证金才是真正的金矿。

那年头证券行业没像今天那样管得严,控制人挪用保证金是常有的事。

对于周氏而言,这笔保证金,就是周家集团最重要的资金来源之一,而且还是不要利息的。

再后来,周氏牵扯到上海地区一个震动全国的贪腐案中,整个集团随之垮台,这是后话。

但富友证券这段经历,让年纪轻轻的我学习到一件事:如何获得廉价资金,是企业运营的重中之重。

不过,这是走钢丝的活,稍有不慎,粉身碎骨。

 

最近热播的《人民的名义》,我倒是特别同情那位老板蔡成功。

这个角色反映出两点:1、搞资金风险巨大;2、底层的民营企业老板,活得极其不容易。

蔡成功在煤矿这件事上赔了血本,最终引发银行断贷、高利贷逼债,窟窿越搞越大,有大几亿之多。

没辙,只有一条路还能保命:坐监去算求。

很多老板想辙弄不要利息的资金,前文说的周正毅是一例。

后来证券行业明令券商不得挪用保证金,慢慢这种不要利息的资金,是没有了。

而今天,易到和乐视又是一例。

但这里不是不要利息,而是利息极高。

乐视对资金的饥渴,使得它明知鸠有毒,却不喝不行。

 

周航在易到内部被架空,不是什么新闻。乐视进入易到后,周航就基本靠边站了。

乐视对易到的看重,我和一位朋友分析过,什么智能汽车搞出来给易到就生态化反了,纯属梦呓。核心利益就一点:这里是一个庞大的资金入口。

用充返活动,吸引用户存入资金,从而获得巨额现金。

到底易到不是金融企业,这种钱,并不在一行三会的视野之内,也不存在什么禁止资金池这一说。

这一大笔款项入账,用于什么呢?

买余额宝,你信么?

但这钱,真有毒。

“利息”极高,用户存一百当一百五、一百七甚至两百用,你说代价大不大。

乐视自己都患了资金饥渴症晚期,指望乐视去贴这个窟窿,怎么可能!

 

所以,这不是拖不拖的问题。

而是两个字:没辙。

我以前说过,乐视是不是庞氏骗局,还有待考证。

但易到这个窟窿,很明显就有庞氏的特点。

张三充了一百,得两百,在那里美滋滋使用,而这个一百,便部分去还了司机李四的账,部分,估计填其它窟窿去了。

这个盘子转啊转啊转啊,终于有一天。眼瞅着就快赚不下去了。

要命的是,充返活动吧,搞到企业报表上,那就是巨额的负债。

如果仅考虑商业,真是没什么大佬敢接了。

 

我身边有几个朋友炒乐视股票,还有一位下注还不小。

小四那部什么CG电影大败亏输,我和其中一位说,还有乐视股票么?赶紧抛了吧。

因为,乐视影业,也是整个乐视大厦的现金流业务。

乐视搞什么硬件免费,充n年的会员费,都是现金业务。

但天底下,真没什么资金,是不需要支付代价的。

低估了这份代价,就会变成蔡成功。

只是,蔡老板真不是野心催的。

有些人,则是自作孽啊!

 

以下文字,不玩阴阳师的看不懂。

我把座敷童子命名为:易到。

 

—— 首发 扯氮集 ——

版权声明  及  商业合作

作者执教于上海交通大学媒体与设计学院,天奇阿米巴创投基金投资合伙人

易到的问题是乐视能不拖的么?,首发于扯氮集

]]>
0
<![CDATA[gRPC的那些事 - interceptor]]> http://www.udpwork.com/item/16216.html http://www.udpwork.com/item/16216.html#reviews Mon, 17 Apr 2017 13:41:16 +0800 鸟窝 http://www.udpwork.com/item/16216.html gRPC-Go 增加了拦截器(interceptor)的功能, 就像Java Servlet中的 filter一样,可以对RPC的请求和响应进行拦截处理,而且既可以在客户端进行拦截,也可以对服务器端进行拦截。

利用拦截器,可以对gRPC进行扩展,利用社区的力量将gRPC发展壮大,也可以让开发者更灵活地处理gRPC流程中的业务逻辑。下面列出了利用拦截器实现的一些功能框架:

  1. Go gRPC Middleware:提供了拦截器的interceptor链式的功能,可以将多个拦截器组合成一个拦截器链,当然它还提供了其它的功能,所以以gRPC中间件命名。
  2. grpc-multi-interceptor: 是另一个interceptor链式功能的库,也可以将单向的或者流式的拦截器组合。
  3. grpc_auth: 身份验证拦截器
  4. grpc_ctxtags: 为上下文增加Tagmap对象
  5. grpc_zap: 支持zap日志框架
  6. grpc_logrus: 支持logrus日志框架
  7. grpc_prometheus: 支持prometheus
  8. otgrpc: 支持opentracing/zipkin
  9. grpc_opentracing:支持opentracing/zipkin
  10. grpc_retry: 为客户端增加重试的功能
  11. grpc_validator: 为服务器端增加校验的功能
  12. xrequestid: 将request id 设置到context中
  13. go-grpc-interceptor: 解析Accept-Language并设置到context
  14. requestdump: 输出request/response

也有其它一些文章介绍的利用拦截器的例子,如下面的两篇文章:
Introduction to OAuth on gRPCgRPC实践 拦截器 Interceptor

相信会有更多有趣的拦截器被贡献出来。

注意,服务器只能配置一个 unary interceptor和 stream interceptor,否则会报错,客户端也是,虽然不会报错,但是只有最后一个才起作用。 如果你想配置多个,可以使用前面提到的拦截器链或者自己实现一个。

实现拦截器麻烦吗?一点都不麻烦,相反,非常的简单。

对于服务器端的单向调用的拦截器,只需定义一个UnaryServerInterceptor方法:

1
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

对于服务器端stream调用的拦截器,只需定义一个StreamServerInterceptor方法:

1
type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

方法的参数中包含了上下文,请求和stream以及要调用对象的信息。

对于客户端的单向的拦截,只需定义一个``方法:

1
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error

对于客户端的stream的拦截,只需定义一个``方法:

1
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)

你可以查看上面提到的一些开源的拦截器的实现,它们的实现都不是太复杂,下面我们以一个简单的例子来距离,在方法调用的前后打印一个log。

Server端的拦截器

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
package mainimport (	"log"	"net"	"flag"	pb "github.com/smallnest/grpc/a/pb"	"golang.org/x/net/context"	"google.golang.org/grpc"	"google.golang.org/grpc/reflection")var (	port = flag.String("p", ":8972", "port"))type server struct{}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {	return &pb.HelloReply{Message: "Hello " + in.Name}, nil}func main() {	flag.Parse()	lis, err := net.Listen("tcp", *port)	if err != nil {		log.Fatalf("failed to listen: %v", err)	}	s := grpc.NewServer(grpc.StreamInterceptor(StreamServerInterceptor),		grpc.UnaryInterceptor(UnaryServerInterceptor))	pb.RegisterGreeterServer(s, &server{})	reflection.Register(s)	if err := s.Serve(lis); err != nil {		log.Fatalf("failed to serve: %v", err)	}}func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {	log.Printf("before handling. Info: %+v", info)	resp, err := handler(ctx, req)	log.Printf("after handling. resp: %+v", resp)	return resp, err}// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {	log.Printf("before handling. Info: %+v", info)	err := handler(srv, ss)	log.Printf("after handling. err: %v", err)	return err}

grpc.NewServer可以将拦截器作为参数传入,在提供服务的时候,我们可以看到拦截器打印出log:

12
2017/04/17 23:34:20 before handling. Info: &{Server:0x17309c8 FullMethod:/pb.Greeter/SayHello}2017/04/17 23:34:20 after handling. resp: &HelloReply{Message:Hello world,}

客户端的拦截器

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
package mainimport (	//"context"	"flag"	"log"	"golang.org/x/net/context"	pb "github.com/smallnest/grpc/a/pb"	"google.golang.org/grpc")var (	address = flag.String("addr", "localhost:8972", "address")	name    = flag.String("n", "world", "name"))func main() {	flag.Parse()	// 连接服务器	conn, err := grpc.Dial(*address, grpc.WithInsecure(), grpc.WithUnaryInterceptor(UnaryClientInterceptor),		grpc.WithStreamInterceptor(StreamClientInterceptor))	if err != nil {		log.Fatalf("faild to connect: %v", err)	}	defer conn.Close()	c := pb.NewGreeterClient(conn)	r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: *name})	if err != nil {		log.Fatalf("could not greet: %v", err)	}	log.Printf("Greeting: %s", r.Message)}func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {	log.Printf("before invoker. method: %+v, request:%+v", method, req)	err := invoker(ctx, method, req, reply, cc, opts...)	log.Printf("after invoker. reply: %+v", reply)	return err}func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {	log.Printf("before invoker. method: %+v, StreamDesc:%+v", method, desc)	clientStream, err := streamer(ctx, desc, cc, method, opts...)	log.Printf("before invoker. method: %+v", method)	return clientStream, err}

通过grpc.WithUnaryInterceptor、grpc.WithStreamInterceptor可以将拦截器传递给Dial做参数。在客户端调用的时候,可以查看拦截器输出的日志:

123
2017/04/17 23:34:20 before invoker. method: /pb.Greeter/SayHello, request:&HelloRequest{Name:world,}2017/04/17 23:34:20 after invoker. reply: &HelloReply{Message:Hello world,}2017/04/17 23:34:20 Greeting: Hello world

通过这个简单的例子,你可以很容易的了解拦截器的开发。unary和stream两种类型的拦截器可以根据你的gRPC server/client实现的不同,有选择的实现。

]]>
gRPC-Go 增加了拦截器(interceptor)的功能, 就像Java Servlet中的 filter一样,可以对RPC的请求和响应进行拦截处理,而且既可以在客户端进行拦截,也可以对服务器端进行拦截。

利用拦截器,可以对gRPC进行扩展,利用社区的力量将gRPC发展壮大,也可以让开发者更灵活地处理gRPC流程中的业务逻辑。下面列出了利用拦截器实现的一些功能框架:

  1. Go gRPC Middleware:提供了拦截器的interceptor链式的功能,可以将多个拦截器组合成一个拦截器链,当然它还提供了其它的功能,所以以gRPC中间件命名。
  2. grpc-multi-interceptor: 是另一个interceptor链式功能的库,也可以将单向的或者流式的拦截器组合。
  3. grpc_auth: 身份验证拦截器
  4. grpc_ctxtags: 为上下文增加Tagmap对象
  5. grpc_zap: 支持zap日志框架
  6. grpc_logrus: 支持logrus日志框架
  7. grpc_prometheus: 支持prometheus
  8. otgrpc: 支持opentracing/zipkin
  9. grpc_opentracing:支持opentracing/zipkin
  10. grpc_retry: 为客户端增加重试的功能
  11. grpc_validator: 为服务器端增加校验的功能
  12. xrequestid: 将request id 设置到context中
  13. go-grpc-interceptor: 解析Accept-Language并设置到context
  14. requestdump: 输出request/response

也有其它一些文章介绍的利用拦截器的例子,如下面的两篇文章:
Introduction to OAuth on gRPCgRPC实践 拦截器 Interceptor

相信会有更多有趣的拦截器被贡献出来。

注意,服务器只能配置一个 unary interceptor和 stream interceptor,否则会报错,客户端也是,虽然不会报错,但是只有最后一个才起作用。 如果你想配置多个,可以使用前面提到的拦截器链或者自己实现一个。

实现拦截器麻烦吗?一点都不麻烦,相反,非常的简单。

对于服务器端的单向调用的拦截器,只需定义一个UnaryServerInterceptor方法:

1
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

对于服务器端stream调用的拦截器,只需定义一个StreamServerInterceptor方法:

1
type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

方法的参数中包含了上下文,请求和stream以及要调用对象的信息。

对于客户端的单向的拦截,只需定义一个``方法:

1
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error

对于客户端的stream的拦截,只需定义一个``方法:

1
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)

你可以查看上面提到的一些开源的拦截器的实现,它们的实现都不是太复杂,下面我们以一个简单的例子来距离,在方法调用的前后打印一个log。

Server端的拦截器

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
package mainimport (	"log"	"net"	"flag"	pb "github.com/smallnest/grpc/a/pb"	"golang.org/x/net/context"	"google.golang.org/grpc"	"google.golang.org/grpc/reflection")var (	port = flag.String("p", ":8972", "port"))type server struct{}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {	return &pb.HelloReply{Message: "Hello " + in.Name}, nil}func main() {	flag.Parse()	lis, err := net.Listen("tcp", *port)	if err != nil {		log.Fatalf("failed to listen: %v", err)	}	s := grpc.NewServer(grpc.StreamInterceptor(StreamServerInterceptor),		grpc.UnaryInterceptor(UnaryServerInterceptor))	pb.RegisterGreeterServer(s, &server{})	reflection.Register(s)	if err := s.Serve(lis); err != nil {		log.Fatalf("failed to serve: %v", err)	}}func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {	log.Printf("before handling. Info: %+v", info)	resp, err := handler(ctx, req)	log.Printf("after handling. resp: %+v", resp)	return resp, err}// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {	log.Printf("before handling. Info: %+v", info)	err := handler(srv, ss)	log.Printf("after handling. err: %v", err)	return err}

grpc.NewServer可以将拦截器作为参数传入,在提供服务的时候,我们可以看到拦截器打印出log:

12
2017/04/17 23:34:20 before handling. Info: &{Server:0x17309c8 FullMethod:/pb.Greeter/SayHello}2017/04/17 23:34:20 after handling. resp: &HelloReply{Message:Hello world,}

客户端的拦截器

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
package mainimport (	//"context"	"flag"	"log"	"golang.org/x/net/context"	pb "github.com/smallnest/grpc/a/pb"	"google.golang.org/grpc")var (	address = flag.String("addr", "localhost:8972", "address")	name    = flag.String("n", "world", "name"))func main() {	flag.Parse()	// 连接服务器	conn, err := grpc.Dial(*address, grpc.WithInsecure(), grpc.WithUnaryInterceptor(UnaryClientInterceptor),		grpc.WithStreamInterceptor(StreamClientInterceptor))	if err != nil {		log.Fatalf("faild to connect: %v", err)	}	defer conn.Close()	c := pb.NewGreeterClient(conn)	r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: *name})	if err != nil {		log.Fatalf("could not greet: %v", err)	}	log.Printf("Greeting: %s", r.Message)}func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {	log.Printf("before invoker. method: %+v, request:%+v", method, req)	err := invoker(ctx, method, req, reply, cc, opts...)	log.Printf("after invoker. reply: %+v", reply)	return err}func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {	log.Printf("before invoker. method: %+v, StreamDesc:%+v", method, desc)	clientStream, err := streamer(ctx, desc, cc, method, opts...)	log.Printf("before invoker. method: %+v", method)	return clientStream, err}

通过grpc.WithUnaryInterceptor、grpc.WithStreamInterceptor可以将拦截器传递给Dial做参数。在客户端调用的时候,可以查看拦截器输出的日志:

123
2017/04/17 23:34:20 before invoker. method: /pb.Greeter/SayHello, request:&HelloRequest{Name:world,}2017/04/17 23:34:20 after invoker. reply: &HelloReply{Message:Hello world,}2017/04/17 23:34:20 Greeting: Hello world

通过这个简单的例子,你可以很容易的了解拦截器的开发。unary和stream两种类型的拦截器可以根据你的gRPC server/client实现的不同,有选择的实现。

]]>
0