nodejs api 设计
nodejs api 設(shè)計(jì)
轉(zhuǎn)載請(qǐng)注明出處:http://www.haomou.net/2014/08/13/2014_web_token/
來龍去脈
諸如Ember,Angular,Backbone之類的前端框架類庫(kù)正隨著更加精細(xì)的Web應(yīng)用而日益壯大。正因如此,服務(wù)器端的組建也正正在從傳統(tǒng)的任務(wù)中解脫,轉(zhuǎn)而變的更像API。API使得傳統(tǒng)的前端和后端的概念解耦。開發(fā)者可以脫離前端,獨(dú)立的開發(fā)后端,在測(cè)試上獲得更大的便利。這種途徑也使得一個(gè)移動(dòng)應(yīng)用和網(wǎng)頁(yè)應(yīng)用可以使用相同的后端。
當(dāng)使用一個(gè)API時(shí),其中一個(gè)挑戰(zhàn)就是認(rèn)證(authentication)。在傳統(tǒng)的web應(yīng)用中,服務(wù)端成功的返回一個(gè)響應(yīng)(response)依賴于兩件事。一是,他通過一種存儲(chǔ)機(jī)制保存了會(huì)話信息(Session)。每一個(gè)會(huì)話都有它獨(dú)特的信息(id),常常是一個(gè)長(zhǎng)的,隨機(jī)化的字符串,它被用來讓未來的請(qǐng)求(Request)檢索信息。其次,包含在響應(yīng)頭(Header)里面的信息使客戶端保存了一個(gè)Cookie。服務(wù)器自動(dòng)的在每個(gè)子請(qǐng)求里面加上了會(huì)話ID,這使得服務(wù)器可以通過檢索Session中的信息來辨別用戶。這就是傳統(tǒng)的web應(yīng)用逃避HTTP面向無連接的方法(This is how traditional web applications get around the fact that HTTP is stateless)。
API應(yīng)該被設(shè)計(jì)成無狀態(tài)的(Stateless)。這意味著沒有登陸,注銷的方法,也沒有sessions,API的設(shè)計(jì)者同樣也不能依賴Cookie,因?yàn)椴荒鼙WC這些request是由瀏覽器所發(fā)出的。自然,我們需要一個(gè)新的機(jī)制。這篇文章關(guān)注于JSON Web Tokens,簡(jiǎn)寫為JWTs,一個(gè)可能的解決這個(gè)問題的機(jī)制。這篇文章利用Node的Express框架作為后端,以及Backbone作為前端。
常用方法
第一個(gè)是使用在HTTP規(guī)范中所制定的Basic Auth, 它需要在在響應(yīng)中設(shè)定一個(gè)驗(yàn)證身份的Header。客戶端必須在每個(gè)子響應(yīng)是附加它們的憑證(credenbtial),包括它的密碼。如果這些憑證通過了,那么用戶的信息就會(huì)被傳遞到服務(wù)端應(yīng)用。
第二個(gè)方面有點(diǎn)類似,但是使用應(yīng)用自己的驗(yàn)證機(jī)制。通常包括將發(fā)送的憑證與存儲(chǔ)的憑證進(jìn)行檢查。和Basic Auth相比,這種需要在每次請(qǐng)求(call)中發(fā)送憑證。
第三種是OAuth(或者OAuth2)。為第三方的認(rèn)證所設(shè)計(jì),但是更難配置。至少在服務(wù)器端更難。
在使用中,并不會(huì)每次都讓用戶提交用戶名和密碼,通常的情況是客戶端通過一些可靠信息和服務(wù)器交換取token,這個(gè)token作為客服端再次請(qǐng)求的權(quán)限鑰匙。Token通常比密碼更加長(zhǎng)而且復(fù)雜。比如說,JWTs通常會(huì)長(zhǎng)達(dá)150個(gè)字符。一旦獲得了token,在每次調(diào)用API的時(shí)候都要附加上它。然后,這仍然比直接發(fā)送賬戶和密碼更加安全,哪怕是HTTPS。
把token想象成一個(gè)安全的護(hù)照。你在一個(gè)安全的前臺(tái)驗(yàn)證你的身份(通過你的用戶名和密碼),如果你成功驗(yàn)證了自己,你就可以取得這個(gè)。當(dāng)你走進(jìn)大樓的時(shí)候(試圖從調(diào)用API獲取資源),你會(huì)被要求驗(yàn)證你的護(hù)照,而不是在前臺(tái)重新驗(yàn)證。
JWTs
JWTs是一份草案,盡管在本質(zhì)上它是一個(gè)老生常談的一種更加具體的認(rèn)證授權(quán)的機(jī)制。一個(gè)JWT被周期(period)分成了三個(gè)部分。JWT是URL-safe的,意味著可以用來查詢字符參數(shù)。(譯者注:也就是可以脫離URL,不用考慮URL的信息)。關(guān)于Json Web Token,參考 http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
JWT的第一部分是對(duì)一個(gè)簡(jiǎn)單js對(duì)象的編碼后的字符串,這個(gè)js對(duì)象是用來描述這個(gè)token類型以及使用的hash算法。下面的例子展示的是一個(gè)使用了HMAC SHA-256算法的JWT token。
{"typ" : "JWT","alg" : "HS256" }在加密之后,這個(gè)對(duì)象變成了一個(gè)字符串:
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
JWT的第二部分是token的核心,這部分同樣是對(duì)一個(gè)js對(duì)象的編碼,包含了一些摘要信息。有一些是必須的,有一些是選擇性的。實(shí)例如下:
這個(gè)結(jié)構(gòu)被稱為JWT Claims Set。這個(gè)iss是issuer的簡(jiǎn)寫,表明請(qǐng)求的實(shí)體,可以是發(fā)出請(qǐng)求的用戶的信息。exp是expires的簡(jiǎn)寫,是用來指定token的生命周期。(相關(guān)參數(shù)參看:the document)加密編碼之后如下:
1
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
JWT的第三個(gè)部分,是JWT根據(jù)第一部分和第二部分的簽名(Signature)。像這個(gè)樣子:
1
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
最后將上面的合并起來,JWT token如下:
1
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
處理Tokens
我們將用JWT simple模塊去處理token,它將使我們從鉆研如何加密解密中解脫出來。如果你有興趣,可以閱讀這篇說明,或者讀這個(gè)倉(cāng)庫(kù)的源碼。
首先我們將使用下面的命令安裝這個(gè)庫(kù)。記住你可以在命令中加入–save,讓其自動(dòng)的讓其加入到你的package.json文件里面。
在你應(yīng)用的初始環(huán)節(jié),加入以下代碼。這個(gè)代碼引入了Express和JWT simple,而且創(chuàng)建了一個(gè)新的Express應(yīng)用。最后一行設(shè)定了app的一個(gè)名為jwtTokenSecret的變量,其值為‘YOUR_SECRET_STRING’(記得把它換成別的)。
var express = require('express'); var jwt = require('jwt-simple'); var app = express();app.set('jwtTokenSecret', 'YOUR_SECRET_STRING');獲取token
我們需要做的第一件事就是讓客戶端通過他們的賬號(hào)密碼交換token。這里有2種可能的方法在RESTful API里面。第一種是使用POST請(qǐng)求來通過驗(yàn)證,使服務(wù)端發(fā)送帶有token的響應(yīng)。除此之外,你可以使用GET請(qǐng)求,這需要他們使用參數(shù)提供憑證(指URL),或者更好的使用請(qǐng)求頭。
這篇文章的目的是為了解釋token驗(yàn)證的方法而不是基本的用戶名/密碼驗(yàn)證機(jī)制。所以我們假設(shè)我們已經(jīng)通過請(qǐng)求得到了用戶名和密碼:
如果用戶成功驗(yàn)證賬號(hào)和密碼,然后我們生成一個(gè)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() });注意到j(luò)wt.encode()函數(shù)有2個(gè)參數(shù)。第一個(gè)就是一個(gè)需要加密的對(duì)象,第二個(gè)是一個(gè)加密的密鑰。這個(gè)token是由我們之前提到的iss和exp組成的。注意到Moment.js被用來設(shè)置token將在7天之后失效。而res.json()方法用來傳遞這個(gè)JSON對(duì)象給客戶端。
驗(yàn)證Token
客戶端獲取到token后,應(yīng)該在每次向服務(wù)器請(qǐng)求數(shù)據(jù)時(shí)附帶這個(gè)token,然后服務(wù)端驗(yàn)證token。
為了驗(yàn)證JWT,我們需要寫出一些可以完成這些功能的中間件(Middleware):
檢查附上的token
試圖解密
驗(yàn)證token的可用性
如果token是合法的,檢索里面用戶的信息,以及附加到請(qǐng)求的對(duì)象上
我們來寫一個(gè)中間件的框架
為了獲得最大的可擴(kuò)展性,我們?cè)试S客戶端使用一下3個(gè)方法附加我們的token:作為請(qǐng)求鏈接(query)的參數(shù),作為主體的參數(shù)(body),和作為請(qǐng)求頭(Header)的參數(shù)。對(duì)于最后一個(gè),我們將使用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()中間件(譯者注,這個(gè)是Express 3.x的中間件)。
下一步,我們講解析JWT:
如果解析的過程失敗,那么JWT Simple組件將會(huì)拋出一段異常。如果異常發(fā)生了,或者沒有token,我們將會(huì)調(diào)用next()來繼續(xù)處理請(qǐng)求。這代表喆我們無法確定用戶。如果一個(gè)合格的token合法并且被解碼,我們應(yīng)該得到2個(gè)屬性,iss包含著用戶ID以及exp包含token過期的時(shí)間戳。我們將首先處理后者,如果它過期了,我們就拒絕它:
if (decoded.exp <= Date.now()) {
res.end(‘Access token has expired’, 400);
}
如果token依舊合法,我們可以從中檢索出用戶信息,并且附加到請(qǐng)求對(duì)象里面去:
User.findOne({ _id: decoded.iss }, function(err, user) {
req.user = user;
});
最后,將這個(gè)中間件附加到路由里面:
或者匹配一些路由
1
app.all(‘/api/*’, [express.bodyParser(), jwtauth]);
客戶端請(qǐng)求
我們提供了一個(gè)簡(jiǎn)單的get端去獲得一個(gè)遠(yuǎn)端的token。這非常直接了,所以我們不用糾結(jié)細(xì)節(jié),就是發(fā)起一個(gè)請(qǐng)求,傳遞用戶名和密碼,如果請(qǐng)求成功了,我們就會(huì)得到一個(gè)包含著token的響應(yīng)。
我們現(xiàn)在研究的是后續(xù)的請(qǐng)求。一個(gè)方法是通過JQuery的ajaxSetup()方法。這可以直接用來做Ajax請(qǐng)求,或者通過前端框架使用包裝過的Ajax方法。比如,假設(shè)我們將我們的請(qǐng)求使用window.localStorage.setItem(‘token’, ‘the-long-access-token’);放在本地存儲(chǔ)(Local Storage)里面,我們可以通過這種方法將token附加到請(qǐng)求頭里面:
var token = window.localStorage.getItem('token');if (token) {$.ajaxSetup({headers: {'x-access-token': token}}); }很簡(jiǎn)單,但是這會(huì)劫持所有Ajax請(qǐng)求,如果這里有一個(gè)token在本地存儲(chǔ)里面。它將會(huì)附加到一個(gè)名為x-access-token的Header里面。
bear token
關(guān)于bear token,參看 RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage , 目前國(guó)內(nèi)各大網(wǎng)站都是用不同的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) 授權(quán)一個(gè)手機(jī) App (Client) 以我 (Resource Owner) 的名義去 Facebook (Resource Server) 取得我的朋友名單 (Protected Resource)。OAuth 2.0 定義Access Token 是 Resource Server 用來認(rèn)證的唯一方式,有了這個(gè), Resource Server 就不需要再提供其他認(rèn)證方式,例如賬號(hào)密碼。
然而在 RFC 6749 里面只定義抽象的概念,細(xì)節(jié)如 Access Token 格式、怎么傳到 Resource Server ,以及 Access Token 無效時(shí), Resource Server 怎么處理,都沒有定義。所以在 RFC 6750 另外定義了 Bearer Token 的用法。Bearer Token 是一種 Access Token ,由 Authorization Server 在 Resource Owner 的允許下核發(fā)給 Client ,Resource Server 只要認(rèn)在這個(gè) Token 就可以認(rèn)定 Client 已經(jīng)獲取 Resource Owner 的許可,不需要用密碼學(xué)的方式來驗(yàn)證這個(gè) Token 的真?zhèn)巍jP(guān)于Token 被偷走的安全性問題,另一篇再說。
Bearer Token 的格式
1
Bearer XXXXXXXX
其中 XXXXXXXX 的格式為 b64token ,ABNF 的定義:
1
b64token = 1*( ALPHA / DIGIT / “-” / “.” / “_” / “~” / “+” / “/” ) *”=”
寫成 Regular Expression 即是:
1
/[A-Za-z0-9-._~+\/]+=*/
關(guān)于Bear Token還是打算另起一篇,詳細(xì)說明:Bearer Token
express-jwt實(shí)例
下面給一個(gè)具體的實(shí)例,這個(gè)例子的客戶端是web app,使用AngularJS框架。服務(wù)端使用NodeJS做的RESTful API接口,客戶端直接調(diào)用接口數(shù)據(jù),其中使用了token認(rèn)證機(jī)制。
當(dāng)用戶把他的授權(quán)信息發(fā)過來的時(shí)候, Node.js 服務(wù)檢查是否正確,然后返回一個(gè)基于用戶信息的唯一 token 。 AngularJS 應(yīng)用把 token 保存在用戶的 SessionStorage ,之后的在發(fā)送請(qǐng)求的時(shí)候,在請(qǐng)求頭里面加上包含這個(gè) token 的 Authorization。如果 endpoint 需要確認(rèn)用戶授權(quán),服務(wù)端檢查驗(yàn)證這個(gè) token,然后如果成功了就返回?cái)?shù)據(jù),如果失敗了返回 401 或者其它的異常。
用到的技術(shù):
AngularJS
NodeJS ( express.js, express-jwt 和 moongoose)
MongoDB
Redis (備用,用于記錄用戶退出登錄時(shí)候還沒有超時(shí)的token)
客戶端 : AngularJS 部分
首先,我們來創(chuàng)建我們的 AdminUserCtrl controller 和處理 login/logout 動(dòng)作。
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("/");}}} ]);這個(gè) controller 用了兩個(gè) service: UserService 和 AuthenticationService。第一個(gè)處理調(diào)用 REST api 用證書。后面一個(gè)處理用戶的認(rèn)證。它只有一個(gè)布爾值,用來表示用戶是否被授權(quán)。
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() {}} });好了,我們需要做張登陸頁(yè)面:
<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>當(dāng)用戶發(fā)送他的信息過來,我們的 controller 把內(nèi)容發(fā)送到 Node.js 服務(wù)器,如果信息可用,我們把 AuthenticationService里面的 isLogged 設(shè)為 true。我們把從服務(wù)端發(fā)過來的 token 存起來,以便下次請(qǐng)求的時(shí)候使用。等講到 Node.js 的時(shí)候我們會(huì)看看怎么處理。
好了,我們要往每個(gè)請(qǐng)求里面追加一個(gè)特殊的頭信息了:[Authorization: Bearer ] 。為了實(shí)現(xiàn)這個(gè)需求,我們建立一個(gè)服務(wù),叫 TokenInterceptor。
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);}}; });然后我們把這個(gè)interceptor 追加到 $httpProvider :
app.config(function (httpProvider)?{httpProvider.interceptors.push(‘TokenInterceptor’);
});
然后,我們要開始配置路由了,讓 AngularJS 知道哪些需要授權(quán),在這里,我們需要檢查用戶是否已經(jīng)被授權(quán),也就是查看 AuthenticationService 的 isLogged 值。
服務(wù)端: Node.js + MongoDB 部分
為了在我們的 RESTful api 處理授權(quán)信息,我們要用到 express-jwt (JSON Web Token) 來生成一個(gè)唯一 Token,基于用戶的信息。以及驗(yàn)證 Token。
首先,我們?cè)?MongoDB 里面創(chuàng)建一個(gè)用戶的 Schema。我們還要?jiǎng)?chuàng)建調(diào)用一個(gè)中間件,在創(chuàng)建和保存用戶信息到數(shù)據(jù)庫(kù)之前,用于加密密碼。還有我們需要一個(gè)方法來解密密碼,當(dāng)收到用戶請(qǐng)求的時(shí)候,檢查是否在數(shù)據(jù)庫(kù)里面有匹配的。
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);}); };然后我們開始寫授權(quán)用戶和創(chuàng)建 Token 的方法:
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 中間件加到所有的,訪問時(shí)需要授權(quán)的路由上面:
/* 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);上面這個(gè)實(shí)例就采用了token的驗(yàn)證方式構(gòu)建了api接口,但是有兩個(gè)問題需要解決:
用戶退出登錄,但是token并沒有失效,因?yàn)榉?wù)端沒有刪除這個(gè)token
token失效了,怎么辦,如果還是讓用于登錄重新獲取token,會(huì)體驗(yàn)不好。應(yīng)該有token刷新機(jī)制。
使用Redis解決問題1
解決方法是:當(dāng)用戶點(diǎn)了 logout 按鈕的時(shí)候,Token 只會(huì)保存一段時(shí)間,就是你用 jsonwebtoken 登陸之后,token 有效的這段時(shí)間,我們將這個(gè)token存放在Redis中,生存時(shí)間也是jwt獲取這個(gè)token的時(shí)間。這個(gè)時(shí)間到期后,token 會(huì)被 redis 自動(dòng)刪掉。最后,我們創(chuàng)建一個(gè) nodejs 的中間件,檢查所有受限 endopoint 用的 token 是否存在 Redis 數(shù)據(jù)庫(kù)中。
NodeJS 配置 Reids
然后,我們來創(chuàng)建一個(gè)方法,用來檢查提供的 token 是不是被
Token 管理和中間件
為了在 Redis 中保存 Token,我們要?jiǎng)?chuàng)建一個(gè)方法來拿到請(qǐng)求中的 Header 的 Token 參數(shù),然后把它作為 Redis 的 key 保存起來。值是什么我們不管它。
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;} };然后,再創(chuàng)建一個(gè)中間件來驗(yàn)證一下 token,當(dāng)用戶發(fā)起請(qǐng)求的時(shí)候:
// 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 這個(gè)方法,是一個(gè)中間件,用來拿到請(qǐng)求頭中的 token,然后在 Redis 里面查找它。如果 token 被發(fā)現(xiàn)了,我們就發(fā) HTTP 401.否則我們就繼續(xù)工作流,讓請(qǐng)求訪問 API。
我們要在用戶點(diǎn) logout 的時(shí)候,執(zhí)行 expireToken 方法:
exports.logout = function(req, res) {if (req.user) {tokenManager.expireToken(req.headers);delete req.user;return res.send(200);}else {return res.send(401);} }最后我們更新路由,用上新的中間件:
//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);好了,現(xiàn)在我們每次發(fā)送請(qǐng)求的時(shí)候,我們都去解析 token, 然后看看是不是有效的。
這里有整個(gè)項(xiàng)目的源代碼
refresh token解決問題2
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其實(shí)就是授權(quán)失敗的部分,這里面的處理方法是返回到登錄授權(quán)頁(yè)面。
這里面考慮的方法是,如果是token超時(shí),使用refresh_token來?yè)Q取新的token。這個(gè)refresh_token,是一開始核發(fā)的時(shí)候一塊發(fā)布給客戶端的,這里就不能使用上面這個(gè)bear token了,要自己處理一下token的問題。
思路1:在user中記錄token超時(shí)時(shí)間,計(jì)算一下剩余時(shí)間,如果剩余時(shí)間比如說小于1分鐘,開始核發(fā)新的token,客戶端自動(dòng)使用新的token,等退出時(shí),就不核發(fā)新的token。
謝謝!
轉(zhuǎn)載請(qǐng)注明出處:http://www.haomou.net/2014/08/13/2014_web_token/
總結(jié)
以上是生活随笔為你收集整理的nodejs api 设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: k8s Storage Classes
- 下一篇: OpenSesame for Mac(a