使用json web token
由来
作了这么长时间的web开发,从JAVA EE中的jsf,spring,hibernate框架,到spring web MVC,到用php框架thinkPHP,到如今的nodejs,我本身的见解是愈来愈喜欢干净整洁的web层,以前用jsf开发作view层的时候,用的primefaces作的界面显示,虽然primefaces的确提供了很大的便利,可让开发人员专一于业务逻辑开发,这样其实就省去了前端开发的工做。然后来发现有些客户须要的展示形式很难实现,或者经过拼凑的方法实现的结果效率不高。使用不灵活,后来本身渐渐的转向了作前端工程师。spring WEB MVC能够作到干净整洁的web层,能够作到web层分离,经过ajax和服务端通讯。如今在学习AngularJS框架,后台数据服务端打算用REST风格的接口来作,这个在先后台交互上就要考虑数据通讯的安全问题,关于这个在关于SESSION的理解一文中其实有提到的。
###前因后果
诸如Ember,Angular,Backbone之类的前端框架类库正随着更加精细的Web应用而日益壮大。正因如此,服务器端的组建也正正在从传统的任务中解脱,转而变的更像API。API使得传统的前端和后端的概念解耦。开发者能够脱离前端,独立的开发后端,在测试上得到更大的便利。这种途径也使得一个移动应用和网页应用可使用相同的后端。php
当使用一个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)。html
API应该被设计成无状态的(Stateless)。这意味着没有登录,注销的方法,也没有sessions,API的设计者一样也不能依赖Cookie,由于不能保证这些request是由浏览器所发出的。天然,咱们须要一个新的机制。这篇文章关注于JSON Web Tokens,简写为JWTs,一个可能的解决这个问题的机制。这篇文章利用Node的Express框架做为后端,以及Backbone做为前端。前端
###经常使用方法
第一个是使用在HTTP规范中所制定的Basic Auth, 它须要在在响应中设定一个验证身份的Header。客户端必须在每一个子响应是附加它们的凭证(credenbtial),包括它的密码。若是这些凭证经过了,那么用户的信息就会被传递到服务端应用。node
第二个方面有点相似,可是使用应用本身的验证机制。一般包括将发送的凭证与存储的凭证进行检查。和Basic Auth相比,这种须要在每次请求(call)中发送凭证。git
第三种是OAuth(或者OAuth2)。为第三方的认证所设计,可是更难配置。至少在服务器端更难。github
在使用中,并不会每次都让用户提交用户名和密码,一般的状况是客户端经过一些可靠信息和服务器交换取token,这个token做为客服端再次请求的权限钥匙。Token一般比密码更加长并且复杂。好比说,JWTs一般会长达150个字符。一旦得到了token,在每次调用API的时候都要附加上它。而后,这仍然比直接发送帐户和密码更加安全,哪怕是HTTPS。
把token想象成一个安全的护照。你在一个安全的前台验证你的身份(经过你的用户名和密码),若是你成功验证了本身,你就能够取得这个。当你走进大楼的时候(试图从调用API获取资源),你会被要求验证你的护照,而不是在前台从新验证。web
JWTs
JWTs是一份草案,尽管在本质上它是一个老生常谈的一种更加具体的认证受权的机制。一个JWT被周期(period)分红了三个部分。JWT是URL-safe的,意味着能够用来查询字符参数。(译者注:也就是能够脱离URL,不用考虑URL的信息)。关于Json Web Token,参考 http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html ajax
JWT的第一部分是对一个简单js对象的编码后的字符串,这个js对象是用来描述这个token类型以及使用的hash算法。下面的例子展现的是一个使用了HMAC SHA-256算法的JWT token。redis
1 2 3 4
|
{ "typ" : "JWT", "alg" : "HS256" }
|
在加密以后,这个对象变成了一个字符串:
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
JWT的第二部分是token的核心,这部分一样是对一个js对象的编码,包含了一些摘要信息。有一些是必须的,有一些是选择性的。实例以下:算法
1 2 3 4 5
|
{ "iss": "joe", "exp": 1300819380, "http://example.com/is_root": true }
|
这个结构被称为JWT Claims Set。这个iss是issuer的简写,代表请求的实体,能够是发出请求的用户的信息。exp是expires的简写,是用来指定token的生命周期。(相关参数参看:the document)加密编码以后以下:
1
|
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
|
JWT的第三个部分,是JWT根据第一部分和第二部分的签名(Signature)。像这个样子:
1
|
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
|
最后将上面的合并起来,JWT token以下:
1
|
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验证的方法而不是基本的用户名/密码验证机制。因此咱们假设咱们已经经过请求获得了用户名和密码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
User.findOne({ username: username }, function(err, user) { if (err) { // user not found return res.send(401); }
if (!user) { // incorrect username return res.send(401); }
if (!user.validPassword(password)) { // incorrect password return res.send(401); }
// User has authenticated OK res.send(200); });
|
若是用户成功验证帐号和密码,而后咱们生成一个token,返回给用户。
1 2 3 4 5 6 7 8 9 10 11
|
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是合法的,检索里面用户的信息,以及附加到请求的对象上
咱们来写一个中间件的框架
1 2 3 4 5 6 7 8
|
// @file jwtauth.js
var 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:
1
|
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:
1 2 3 4 5 6 7 8 9 10 11 12
|
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过时的时间戳。咱们将首先处理后者,若是它过时了,咱们就拒绝它:
1 2 3
|
if (decoded.exp <= Date.now()) { res.end('Access token has expired', 400); }
|
若是token依旧合法,咱们能够从中检索出用户信息,而且附加到请求对象里面去:
1 2 3
|
User.findOne({ _id: decoded.iss }, function(err, user) { req.user = user; });
|
最后,将这个中间件附加到路由里面:
1 2 3 4 5
|
var jwtauth = require('./jwtauth.js');
app.get('/something', [express.bodyParser(), jwtauth], function(req, res){ // do something });
|
或者匹配一些路由
1
|
app.all('/api/*', [express.bodyParser(), jwtauth]);
|
客户端请求
咱们提供了一个简单的get端去得到一个远端的token。这很是直接了,因此咱们不用纠结细节,就是发起一个请求,传递用户名和密码,若是请求成功了,咱们就会获得一个包含着token的响应。
咱们如今研究的是后续的请求。一个方法是经过JQuery的ajaxSetup()方法。这能够直接用来作Ajax请求,或者经过前端框架使用包装过的Ajax方法。好比,假设咱们将咱们的请求使用window.localStorage.setItem(‘token’, ‘the-long-access-token’);放在本地存储(Local Storage)里面,咱们能够经过这种方法将token附加到请求头里面:
1 2 3 4 5 6 7 8 9
|
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 的格式
其中 XXXXXXXX 的格式为 b64token ,ABNF 的定義:
1
|
b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
|
写成 Regular Expression 便是:
1
|
/[A-Za-z0-9\-\._~\+\/]+=*/
|
关于Bear Token仍是打算另起一篇,详细说明:Bearer Token
express-jwt实例
下面给一个具体的实例,这个例子的客户端是web app,使用AngularJS框架。服务端使用NodeJS作的RESTful API接口,客户端直接调用接口数据,其中使用了token认证机制。
当用户把他的受权信息发过来的时候, Node.js 服务检查是否正确,而后返回一个基于用户信息的惟一 token 。 AngularJS 应用把 token 保存在用户的 SessionStorage ,以后的在发送请求的时候,在请求头里面加上包含这个 token 的 Authorization。若是 endpoint 须要确认用户受权,服务端检查验证这个 token,而后若是成功了就返回数据,若是失败了返回 401 或者其它的异常。
用到的技术:
- AngularJS
- NodeJS ( express.js, express-jwt 和 moongoose)
- MongoDB
- Redis (备用,用于记录用户退出登陆时候尚未超时的token)
客户端 : AngularJS 部分
首先,咱们来建立咱们的 AdminUserCtrl controller 和处理 login/logout 动做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
appControllers.controller('AdminUserCtrl', ['$scope', '$location', '$window', 'UserService', 'AuthenticationService', function AdminUserCtrl($scope, $location, $window, UserService, AuthenticationService) { //Admin User Controller (login, logout) $scope.logIn = function logIn(username, password) { if (username !== undefined && password !== undefined) { UserService.logIn(username, password).success(function(data) { AuthenticationService.isLogged = true; $window.sessionStorage.token = data.token; $location.path("/admin"); }).error(function(status, data) { console.log(status); console.log(data); }); } } $scope.logout = function logout() { if (AuthenticationService.isLogged) { AuthenticationService.isLogged = false; delete $window.sessionStorage.token; $location.path("/"); } } } ]);
|
这个 controller 用了两个 service: UserService 和 AuthenticationService。第一个处理调用 REST api 用证书。后面一个处理用户的认证。它只有一个布尔值,用来表示用户是否被受权。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
appServices.factory('AuthenticationService', function() { var auth = { isLogged: false } return auth; }); appServices.factory('UserService', function($http) { return { logIn: function(username, password) { return $http.post(options.api.base_url + '/login', {username: username, password: password}); }, logOut: function() { } } });
|
好了,咱们须要作张登录页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
<form class="form-horizontal" role="form"> <div class="form-group"> <label for="inputUsername" class="col-sm-4 control-label">Username</label> <div class="col-sm-4"> <input type="text" class="form-control" id="inputUsername" placeholder="Username" ng-model="login.email"> </div> </div> <div class="form-group"> <label for="inputPassword" class="col-sm-4 control-label">Password</label> <div class="col-sm-4"> <input type="password" class="form-control" id="inputPassword" placeholder="Password" ng-model="login.password"> </div> </div> <div class="form-group"> <div class="col-sm-offset-4 col-sm-10"> <button type="submit" class="btn btn-default" ng-click="logIn(login.email, login.password)">Log In</button> </div> </div> </form>
|
当用户发送他的信息过来,咱们的 controller 把内容发送到 Node.js 服务器,若是信息可用,咱们把 AuthenticationService里面的 isLogged 设为 true。咱们把从服务端发过来的 token 存起来,以便下次请求的时候使用。等讲到 Node.js 的时候咱们会看看怎么处理。
好了,咱们要往每一个请求里面追加一个特殊的头信息了:[Authorization: Bearer ] 。为了实现这个需求,咱们创建一个服务,叫 TokenInterceptor。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
appServices.factory('TokenInterceptor', function ($q, $window, AuthenticationService) { return { request: function (config) { config.headers = config.headers || {}; if ($window.sessionStorage.token) { config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token; } return config; }, response: function (response) { return response || $q.when(response); } }; });
|
而后咱们把这个interceptor 追加到 $httpProvider :
1 2 3
|
app.config(function ($httpProvider) { $httpProvider.interceptors.push('TokenInterceptor'); });
|
而后,咱们要开始配置路由了,让 AngularJS 知道哪些须要受权,在这里,咱们须要检查用户是否已经被受权,也就是查看 AuthenticationService 的 isLogged 值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
app.config(['$locationProvider', '$routeProvider', function($location, $routeProvider) { $routeProvider. when('/', { templateUrl: 'partials/post.list.html', controller: 'PostListCtrl' }). when('/post/:id', { templateUrl: 'partials/post.view.html', controller: 'PostViewCtrl' }). when('/tag/:tagName', { templateUrl: 'partials/post.list.html', controller: 'PostListTagCtrl' }). when('/admin', { templateUrl: 'partials/admin.post.list.html', controller: 'AdminPostListCtrl', access: { requiredLogin: true } }). when('/admin/post/create', { templateUrl: 'partials/admin.post.create.html', controller: 'AdminPostCreateCtrl', access: { requiredLogin: true } }). when('/admin/post/edit/:id', { templateUrl: 'partials/admin.post.edit.html', controller: 'AdminPostEditCtrl', access: { requiredLogin: true } }). when('/admin/login', { templateUrl: 'partials/admin.login.html', controller: 'AdminUserCtrl' }). when('/admin/logout', { templateUrl: 'partials/admin.logout.html', controller: 'AdminUserCtrl', access: { requiredLogin: true } }). otherwise({ redirectTo: '/' }); }]);
app.run(function($rootScope, $location, $window, AuthenticationService) { $rootScope.$on("$routeChangeStart", function(event, nextRoute, currentRoute) { //redirect only if both isLogged is false and no token is set if (nextRoute != null && nextRoute.access != null && nextRoute.access.requiredLogin && !AuthenticationService.isLogged && !$window.sessionStorage.token) {
$location.path("/admin/login"); } }); });
|
服务端: Node.js + MongoDB 部分
为了在咱们的 RESTful api 处理受权信息,咱们要用到 express-jwt (JSON Web Token) 来生成一个惟一 Token,基于用户的信息。以及验证 Token。
首先,咱们在 MongoDB 里面建立一个用户的 Schema。咱们还要建立调用一个中间件,在建立和保存用户信息到数据库以前,用于加密密码。还有咱们须要一个方法来解密密码,当收到用户请求的时候,检查是否在数据库里面有匹配的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
var Schema = mongoose.Schema; // User schema var User = new Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true} }); // Bcrypt middleware on UserSchema User.pre('save', function(next) { var user = this; if (!user.isModified('password')) return next(); bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) { if (err) return next(err); bcrypt.hash(user.password, salt, function(err, hash) { if (err) return next(err); user.password = hash; next(); }); }); }); //Password verification User.methods.comparePassword = function(password, cb) { bcrypt.compare(password, this.password, function(err, isMatch) { if (err) return cb(err); cb(isMatch); }); };
|
而后咱们开始写受权用户和建立 Token 的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
exports.login = function(req, res) { var username = req.body.username || ''; var password = req.body.password || ''; if (username == '' || password == '') { return res.send(401); } db.userModel.findOne({username: username}, function (err, user) { if (err) { console.log(err); return res.send(401); } user.comparePassword(password, function(isMatch) { if (!isMatch) { console.log("Attempt failed to login with " + user.username); return res.send(401); } var token = jwt.sign(user, secret.secretToken, { expiresInMinutes: 60 }); return res.json({token:token}); }); }); };
|
最后,咱们须要把 jwt 中间件加到全部的,访问时须要受权的路由上面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
/* Get all published posts */ app.get('/post', routes.posts.list); /* Get all posts */ app.get('/post/all', jwt({secret: secret.secretToken}), routes.posts.listAll); /* Get an existing post. Require url */ app.get('/post/:id', routes.posts.read); /* Get posts by tag */ app.get('/tag/:tagName', routes.posts.listByTag); /* Login */ app.post('/login', routes.users.login); /* Logout */ app.get('/logout', routes.users.logout); /* Create a new post. Require data */ app.post('/post', jwt({secret: secret.secretToken}), routes.posts.create); /* Update an existing post. Require id */ app.put('/post', jwt({secret: secret.secretToken}), routes.posts.update); /* Delete an existing post. Require id */ app.delete('/post/:id', jwt({secret: secret.secretToken}), routes.posts.delete);
|
上面这个实例就采用了token的验证方式构建了api接口,可是有两个问题须要解决:
- 用户退出登陆,可是token并无失效,由于服务端没有删除这个token
- token失效了,怎么办,若是仍是让用于登陆从新获取token,会体验很差。应该有token刷新机制。
###使用Redis解决问题1
解决方法是:当用户点了 logout 按钮的时候,Token 只会保存一段时间,就是你用 jsonwebtoken 登录以后,token 有效的这段时间,咱们将这个token存放在Redis中,生存时间也是jwt获取这个token的时间。这个时间到期后,token 会被 redis 自动删掉。最后,咱们建立一个 nodejs 的中间件,检查全部受限 endopoint 用的 token 是否存在 Redis 数据库中。 NodeJS 配置 Reids
1 2 3 4 5 6 7 8 9 10 11 12 13
|
var redis = require('redis'); var redisClient = redis.createClient(6379); redisClient.on('error', function (err) { console.log('Error ' + err); }); redisClient.on('connect', function () { console.log('Redis is ready'); }); exports.redis = redis; exports.redisClient = redisClient;
|
而后,咱们来建立一个方法,用来检查提供的 token 是否是被
Token 管理和中间件
为了在 Redis 中保存 Token,咱们要建立一个方法来拿到请求中的 Header 的 Token 参数,而后把它做为 Redis 的 key 保存起来。值是什么咱们无论它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
var redisClient = require('./redis_database').redisClient; var TOKEN_EXPIRATION = 60; var TOKEN_EXPIRATION_SEC = TOKEN_EXPIRATION * 60; exports.expireToken = function(headers) { var token = getToken(headers); if (token != null) { redisClient.set(token, { is_expired: true }); redisClient.expire(token, TOKEN_EXPIRATION_SEC); } }; var getToken = function(headers) { if (headers && headers.authorization) { var authorization = headers.authorization; var part = authorization.split(' '); if (part.length == 2) { var token = part[1]; return part[1]; } else { return null; } } else { return null; } };
|
而后,再建立一个中间件来验证一下 token,当用户发起请求的时候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
// Middleware for token verification exports.verifyToken = function (req, res, next) { var token = getToken(req.headers); redisClient.get(token, function (err, reply) { if (err) { console.log(err); return res.send(500); } if (reply) { res.send(401); } else { next(); } }); };
|
verifyToken 这个方法,是一个中间件,用来拿到请求头中的 token,而后在 Redis 里面查找它。若是 token 被发现了,咱们就发 HTTP 401.不然咱们就继续工做流,让请求访问 API。
咱们要在用户点 logout 的时候,执行 expireToken 方法:
1 2 3 4 5 6 7 8 9 10 11
|
exports.logout = function(req, res) { if (req.user) { tokenManager.expireToken(req.headers); delete req.user; return res.send(200); } else { return res.send(401); } }
|
最后咱们更新路由,用上新的中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
//Login app.post('/user/signin', routes.users.signin); //Logout app.get('/user/logout', jwt({secret: secret.secretToken}), routes.users.logout); //Get all posts app.get('/post/all', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.listAll); //Create a new post app.post('/post', jwt({secret: secret.secretToken}), tokenManager.verifyToken , routes.posts.create); //Edit the post id app.put('/post', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.update); //Delete the post id app.delete('/post/:id', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.delete);
|
好了,如今咱们每次发送请求的时候,咱们都去解析 token, 而后看看是否是有效的。
这里有整个项目的源代码
refresh token解决问题2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
appServices.factory('TokenInterceptor', function ($q, $window, $location, AuthenticationService) { return { request: function (config) { config.headers = config.headers || {}; if ($window.sessionStorage.token) { config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token; } return config; },
requestError: function(rejection) { return $q.reject(rejection); },
/* Set Authentication.isAuthenticated to true if 200 received */ response: function (response) { if (response != null && response.status == 200 && $window.sessionStorage.token && !AuthenticationService.isAuthenticated) { AuthenticationService.isAuthenticated = true; } return response || $q.when(response); },
/* Revoke client authentication if 401 is received */ responseError: function(rejection) { if (rejection != null && rejection.status === 401 && ($window.sessionStorage.token || AuthenticationService.isAuthenticated)) { delete $window.sessionStorage.token; AuthenticationService.isAuthenticated = false; $location.path("/admin/login"); }
return $q.reject(rejection); } }; });
|
上面代码中的最后一部分responseError其实就是受权失败的部分,这里面的处理方法是返回到登陆受权页面。这里面考虑的方法是,若是是token超时,使用refresh_token来换取新的token。这个refresh_token,是一开始核发的时候一块发布给客户端的,这里就不能使用上面这个bear token了,要本身处理一下token的问题。思路1:在user中记录token超时时间,计算一下剩余时间,若是剩余时间好比说小于1分钟,开始核发新的token,客户端自动使用新的token,等退出时,就不核发新的token。