原文地址:http://www.haomou.net/2014/08/13/2014_bare_token/

来龙去脉

诸如Ember,Angular,Backbone之类的前端框架类库正随着更加精细的Web应用而日益壮大。正因如此,服务器端的组建也正正在从传统的任务中解脱,转而变的更像API。API使得传统的前端和后端的概念解耦。开发者可以脱离前端,独立的开发后端,在测试上获得更大的便利。这种途径也使得一个移动应用和网页应用可以使用相同的后端。

当使用一个API时,其中一个挑战就是认证(authentication)。在传统的web应用中,服务端成功的返回一个响应(response)依赖于两件事。一是,他通过一种存储机制保存了会话信息(Session)。每一个会话都有它独特的信息(id),常常是一个长的,随机化的字符串,它被用来让未来的请求(Request)检索信息。其次,包含在响应头(Header)里面的信息使客户端保存了一个Cookie。服务器自动的在每个子请求里面加上了会话ID,这使得服务器可以通过检索Session中的信息来辨别用户。这就是传统的web应用逃避HTTP面向无连接的方法(This is how traditional web applications get around the fact that HTTP is stateless)。

API应该被设计成无状态的(Stateless)。这意味着没有登陆,注销的方法,也没有sessions,API的设计者同样也不能依赖Cookie,因为不能保证这些request是由浏览器所发出的。自然,我们需要一个新的机制。这篇文章关注于JSON Web Tokens,简写为JWTs,一个可能的解决这个问题的机制。这篇文章利用Node的Express框架作为后端,以及Backbone作为前端。

常用方法

第一个是使用在HTTP规范中所制定的Basic Auth, 它需要在在响应中设定一个验证身份的Header。客户端必须在每个子响应是附加它们的凭证(credenbtial),包括它的密码。如果这些凭证通过了,那么用户的信息就会被传递到服务端应用。

第二个方面有点类似,但是使用应用自己的验证机制。通常包括将发送的凭证与存储的凭证进行检查。和Basic Auth相比,这种需要在每次请求(call)中发送凭证。

第三种是OAuth(或者OAuth2)。为第三方的认证所设计,但是更难配置。至少在服务器端更难。

在使用中,并不会每次都让用户提交用户名和密码,通常的情况是客户端通过一些可靠信息和服务器交换取token,这个token作为客服端再次请求的权限钥匙。Token通常比密码更加长而且复杂。比如说,JWTs通常会长达150个字符。一旦获得了token,在每次调用API的时候都要附加上它。然后,这仍然比直接发送账户和密码更加安全,哪怕是HTTPS。
把token想象成一个安全的护照。你在一个安全的前台验证你的身份(通过你的用户名和密码),如果你成功验证了自己,你就可以取得这个。当你走进大楼的时候(试图从调用API获取资源),你会被要求验证你的护照,而不是在前台重新验证。

JWT(JSON Web Token)

JWTs是一份草案,尽管在本质上它是一个老生常谈的一种更加具体的认证授权的机制。一个JWT被周期(period)分成了三个部分。JWT是URL-safe的,意味着可以用来查询字符参数。(译者注:也就是可以脱离URL,不用考虑URL的信息)。

JWT的第一部分是对一个简单js对象的编码后的字符串,这个js对象是用来描述这个token类型以及使用的hash算法。下面的例子展示的是一个使用了HMAC SHA-256算法的JWT token。

{"typ" : "JWT","alg" : "HS256"
}

在加密之后,这个对象变成了一个字符串:
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
JWT的第二部分是token的核心,这部分同样是对一个js对象的编码,包含了一些摘要信息。有一些是必须的,有一些是选择性的。实例如下:

{"iss": "joe","exp": 1300819380,"http://example.com/is_root": true
}

这个结构被称为JWT Claims Set。这个iss是issuer的简写,表明请求的实体,可以是发出请求的用户的信息。exp是expires的简写,是用来指定token的生命周期。(相关参数参看:the document)加密编码之后如下:

eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

JWT的第三个部分,是JWT根据第一部分和第二部分的签名(Signature)。像这个样子:

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

最后将上面的合并起来,JWT token如下:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

处理Tokens

我们将用JWT simple模块去处理token,它将使我们从钻研如何加密解密中解脱出来。如果你有兴趣,可以阅读这篇说明,或者读这个仓库的源码。
首先我们将使用下面的命令安装这个库。记住你可以在命令中加入–save,让其自动的让其加入到你的package.json文件里面。

npm install jwt-simple

在你应用的初始环节,加入以下代码。这个代码引入了Express和JWT simple,而且创建了一个新的Express应用。最后一行设定了app的一个名为jwtTokenSecret的变量,其值为‘YOUR_SECRET_STRING’(记得把它换成别的)。

var express = require('express');
var jwt = require('jwt-simple');
var app = express();app.set('jwtTokenSecret', 'YOUR_SECRET_STRING');

获取token

我们需要做的第一件事就是让客户端通过他们的账号密码交换token。这里有2种可能的方法在RESTful API里面。第一种是使用POST请求来通过验证,使服务端发送带有token的响应。除此之外,你可以使用GET请求,这需要他们使用参数提供凭证(指URL),或者更好的使用请求头。
这篇文章的目的是为了解释token验证的方法而不是基本的用户名/密码验证机制。所以我们假设我们已经通过请求得到了用户名和密码:

User.findOne({ username: username }, function(err, user) {if (err) {// user not foundreturn res.send(401);}if (!user) {// incorrect usernamereturn res.send(401);}if (!user.validPassword(password)) {// incorrect passwordreturn res.send(401);}// User has authenticated OKres.send(200);
});

如果用户成功验证账号和密码,然后我们生成一个token,返回给用户。

var expires = moment().add('days', 7).valueOf();
var token = jwt.encode({iss: user.id,exp: expires
}, app.get('jwtTokenSecret'));res.json({token : token,expires: expires,user: user.toJSON()
});

注意到jwt.encode()函数有2个参数。第一个就是一个需要加密的对象,第二个是一个加密的密钥。这个token是由我们之前提到的iss和exp组成的。注意到Moment.js被用来设置token将在7天之后失效。而res.json()方法用来传递这个JSON对象给客户端。

验证Token

客户端获取到token后,应该在每次向服务器请求数据时附带这个token,然后服务端验证token。
为了验证JWT,我们需要写出一些可以完成这些功能的中间件(Middleware):

检查附上的token
试图解密
验证token的可用性
如果token是合法的,检索里面用户的信息,以及附加到请求的对象上
我们来写一个中间件的框架

// @file jwtauth.jsvar UserModel = require('../models/user');
var jwt = require('jwt-simple');module.exports = function(req, res, next) {// code goes here
};

为了获得最大的可扩展性,我们允许客户端使用一下3个方法附加我们的token:作为请求链接(query)的参数,作为主体的参数(body),和作为请求头(Header)的参数。对于最后一个,我们将使用Header x-access-token。

下面是我们的允许在中间件的代码,试图去检索token:

var token = (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];

注意到他为了访问req.body,我们需要首先使用express.bodyParser()中间件(译者注,这个是Express 3.x的中间件)。
下一步,我们讲解析JWT:

if (token) {try {var decoded = jwt.decode(token, app.get('jwtTokenSecret'));// handle token here} catch (err) {return next();}
} else {next();
}

如果解析的过程失败,那么JWT Simple组件将会抛出一段异常。如果异常发生了,或者没有token,我们将会调用next()来继续处理请求。这代表喆我们无法确定用户。如果一个合格的token合法并且被解码,我们应该得到2个属性,iss包含着用户ID以及exp包含token过期的时间戳。我们将首先处理后者,如果它过期了,我们就拒绝它:

if (decoded.exp <= Date.now()) {res.end('Access token has expired', 400);
}

如果token依旧合法,我们可以从中检索出用户信息,并且附加到请求对象里面去:

User.findOne({ _id: decoded.iss }, function(err, user) {req.user = user;
});

最后,将这个中间件附加到路由里面:

var jwtauth = require('./jwtauth.js');app.get('/something', [express.bodyParser(), jwtauth], function(req, res){// do something
});

或者匹配一些路由

app.all('/api/*', [express.bodyParser(), jwtauth]);

客户端请求

我们提供了一个简单的get端去获得一个远端的token。这非常直接了,所以我们不用纠结细节,就是发起一个请求,传递用户名和密码,如果请求成功了,我们就会得到一个包含着token的响应。

我们现在研究的是后续的请求。一个方法是通过JQuery的ajaxSetup()方法。这可以直接用来做Ajax请求,或者通过前端框架使用包装过的Ajax方法。比如,假设我们将我们的请求使用window.localStorage.setItem(‘token’, ‘the-long-access-token’);放在本地存储(Local Storage)里面,我们可以通过这种方法将token附加到请求头里面:

var token = window.localStorage.getItem('token');if (token) {$.ajaxSetup({headers: {'x-access-token': token}});
}

很简单,但是这会劫持所有Ajax请求,如果这里有一个token在本地存储里面。它将会附加到一个名为x-access-token的Header里面。

bear token

关于bear token,请参看RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage , 目前国内各大网站都是用不同的token,也没说必须使用bear token,只有twitter明确说明的是使用bear token。

OAuth 2.0 (RFC 6749) 定义了 Client 如何取得 Access Token 的方法。Client 可以用 Access Token 以 Resource Owner 的名义来向 Resource Server 取得 Protected Resource ,例如我 (Resource Owner) 授权一個手机 App (Client) 以我 (Resource Owner) 的名义去 Facebook (Resource Server) 取得我的朋友名单 (Protected Resource)。OAuth 2.0 定义Access Token 是 Resource Server 用来认证的唯一方式,有了这个, Resource Server 就不需要再提供其他认证方式,例如账号密码。

然而在 RFC 6749 里面只定义抽象的概念,细节如 Access Token 格式、怎么传到 Resource Server ,以及 Access Token 无效时, Resource Server 怎么处理,都没有定义。所以在 RFC 6750 另外定义了 Bearer Token 的用法。Bearer Token 是一种 Access Token ,由 Authorization Server 在 Resource Owner 的允许下核发给 Client ,Resource Server 只要认在这个 Token 就可以认定 Client 已经获取 Resource Owner 的许可,不需要用密码学的方式来验证这个 Token 的真伪。关于Token 被偷走的安全性问题,另一篇再说。

Bearer Token 的格式

Bearer XXXXXXXX

其中 XXXXXXXX 的格式 b64token ,ABNF 的定义:

b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

写成 Regular Expression 即是:

/[A-Za-z0-9\-\._~\+\/]+=*/

Bearer Token的相关定义与使用方法相关推荐

  1. php bearer token,php-使用CURL设置Bearer令牌的正确方法

    php-使用CURL设置Bearer令牌的正确方法 我从API端点获取了承载令牌,并设置了以下内容: $authorization = "Bearer 080042cad6356ad5dc0 ...

  2. OAuth 2.0: Bearer Token Usage

    Bearer Token (RFC 6750) 用于OAuth 2.0授权访问资源,任何Bearer持有者都可以无差别地用它来访问相关的资源,而无需证明持有加密key.一个Bearer代表授权范围.有 ...

  3. HTTP Token 使用方式: Basic Token v.s Bearer Token

    前言 在Auth的过程,很常会看到Basic.Bearer 型态的Token,而是用的场景不太一样. Basic用在存取一个网站.网域的时候,Bearer则是用于存取Protect Resource的 ...

  4. 详解OAuth 2.0授权协议(Bearer token)

    OAuth 2.0授权协议 1 认证(Authentication) 2 授权(Authorization) 3 OAuth 2.0与认证机制的联系 4 详解OAuth 2.0授权协议 4.1 授权码 ...

  5. ASP.NET Core Web API 集成测试中使用 Bearer Token

    在 ASP.NET Core Web API 集成测试一文中, 我介绍了ASP.NET Core Web API的集成测试. 在那里我使用了测试专用的Startup类, 里面的配置和开发时有一些区别, ...

  6. openfeign转发bearer token

    其实client_credentials模式,填塞token基本跟这差不多.只是获取token方法不同.本文只是将已登录用户access token转发出去. 1.环境 spring cloud 20 ...

  7. 第7.26节 Python中的@property装饰器定义属性访问方法getter、setter、deleter 详解

    第7.26节 Python中的@property装饰器定义属性访问方法getter.setter.deleter 详解 一.    引言 Python中的装饰器在前面接触过,老猿还没有深入展开介绍装饰 ...

  8. Retrofit2.5是如何解析在接口类中定义的请求方法?

    前言 Retrofit的核心在于它的create方法中使用了动态代理,在这里面主要是loadServiceMethod方法: 以下代码基于Retrofit2.5.0(跟2.3.0代码存在明显不同) p ...

  9. java定义private_java9开始——接口中可以定义private私有方法

    在传统的Java编程中,被广为人知的一个知识点是:java Interface接口中不能定义private私有方法.只允许我们定义public访问权限的方法.抽象方法或静态方法.但是从Java 9 开 ...

最新文章

  1. 第二阶段第八次spring会议
  2. python程序设计课后答案祁瑞华_清华大学出版社-图书详情-《Python 程序设计》
  3. poj2411 Mondriaan's Dream (状压dp+多米诺骨牌问题)
  4. 命令行开启windows下的iis信息服务,开启及配置http端口,开启及配置ftp端口
  5. 从listView1中选择记录到listView2中
  6. CentOS 7下mysqld服务启动失败终极解决方案
  7. springboot-redis读取配置文件
  8. react如何写ajax,请问如何在React中做Ajax 请求?
  9. PreferenceScreen的应用
  10. 基于JAVA的教务排课系统毕业设计
  11. 博途PLC 1200/1500PID PID_Temp 加热制冷双输出+级联控制(串级控制)
  12. python字符串变量替换_Python基于template实现字符串替换
  13. 微信公众号小程序怎么做?
  14. 【课程·研】自然辩证法 | 课堂汇报:工程师的伦理规范
  15. 全球四大国际反垃圾邮件组织介绍
  16. vitamin_baidu
  17. hadoop+Spark实战基于大数据技术之电视收视率企业项目实战
  18. 获取UNIX主机当前时间的函数
  19. netstat -ano命令失效怎么办
  20. 研究:骇客又在合法的苹果Xcode专案上植入恶意程式

热门文章

  1. 让opencv输出人脸检测的得分(置信率),找出一些和脸比较像但是不是脸的负样本
  2. 两个向量夹角的cos值
  3. OpenCV4教程——4.1 窗口相关操作
  4. 为什么 PWA 还没有“干掉”原生应用?
  5. 【转】写给欲采访刘丁宁事件的媒体
  6. K8s9(2-1) k8s中的通信机制, kube-proxy的ipvs模式 ,无头服务,LoadBalancer,ExternalName,外部公有 ip(externalIPs)
  7. 最全MyBatis核心配置文件总结:java运行环境没有安装或配置错误
  8. ife2018 task1
  9. 思科将以6.35亿美元收购网络安全公司OpenDNS
  10. 超好用的在线版 UI mockup 制作工具