我们现在可以创建 RESTful APIs,但是我们不希望每个人都访问我们公开的所有内容。我们希望路线是安全的,并且能够跟踪谁在做什么。
Passport 是一个很棒的模块,也是另一个帮助我们验证请求的中间件。
Passport 为提供者提供了一个简单的 API 来扩展和创建验证用户的策略。在撰写本文时,官方支持的策略有 307 个;然而,你没有理由不能写自己的策略并发布给别人使用。
护照最简单的策略是接受用户名和密码的本地策略。
我们将为这些例子介绍 express 框架,现在您已经了解了它的基本工作原理,我们可以将它们放在一起。
可以安装express
、body-parser
、passport
、passport-local
。Express 是 Node.js 的一个包含电池的网络框架,包括路由和使用中间件的能力:
[~/examples/example-19]$ npm install express body-parser passport passport-local
现在,我们可以将用户存储在一个简单的对象中,供以后参考,如图所示:
var users = {
foo: {
username: 'foo',
password: 'bar',
id: 1
},
bar: {
username: 'bar',
password: 'foo',
id: 2
}
}
一旦我们有了几个用户,就需要设置 passport。当我们创建本地策略的一个实例时,我们需要提供一个verify
回调,在那里我们检查用户名和密码,同时返回一个用户:
var Passport = require( 'passport' ),
LocalStrategy = require( 'passport-local' ).Strategy;
var localStrategy = new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
},
function(username, password, done) {
user = users[ username ];
if ( user == null ) {
return done( null, false, { message: 'Invalid user' } );
}
if ( user.password !== password ) {
return done( null, false, { message: 'Invalid password' } );
}
done( null, user );
}
)
这种情况下的verify
回调是期望用户调用done
。它还允许我们在用户无效或密码错误时提供信息。
现在,我们有了一个策略,可以将它传递给 passport,它允许我们稍后引用它并使用它来验证我们的请求,如下所示:
Passport.use( 'local', localStrategy );
每个应用可以使用多个策略,并通过传递的名称引用每个策略,在本例中为'local'
。
现在,让我们创建我们的服务器,如下所示:
var Express = require( 'express' );
var app = Express( );
我们将不得不使用body-parser
中间件。这将确保,当我们发布到我们的登录路线时,我们可以阅读我们的身体;我们还需要初始化 passport:
var BodyParser = require( 'body-parser' );
app.use( BodyParser.urlencoded( { extended: false } ) );
app.use( BodyParser.json( ) );
app.use( Passport.initialize( ) );
要登录到我们的应用,我们需要创建一个post
路由,使用身份验证作为处理程序之一。这方面的代码如下:
app.post(
'/login',
Passport.authenticate( 'local', { session: false } ),
function ( request, response ) {
}
);
现在,当我们向/login
发送POST
请求时,服务器将验证我们的请求。
一旦通过身份验证,user
属性将被填充到请求对象中,如下所示:
app.post(
'/login',
Passport.authenticate( 'local', { session: false } ),
function ( request, response ) {
response.send( 'User Id ' + request.user.id );
}
);
最后,我们需要像所有其他服务器一样监听请求:
app.listen( 8080, function( ) {
console.log( 'Listening on port 8080' );
});
让我们运行这个例子:
[~/examples/example-19]$ node server.js
Listening on port 8080
现在,当我们在服务器上发送POST
请求时,我们可以验证用户。如果用户没有通过用户名和密码,服务器将返回400 Bad Request
。
如果你不熟悉curl
,你可以使用一个工具,比如高级休息客户端:
https://chrome rest client . app spot . com/
在下面的例子中,我将使用命令行界面curl
。
我们可以通过执行POST
到/login
命令来执行登录请求:
[~]$ curl -X POST http://localhost:8080/login -v
< HTTP/1.1 400 Bad Request
如果用户提供了错误的详细信息,那么401 Unauthorized
将被返回:
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"foo","password":"foo"}' \
-v
< HTTP/1.1 401 Unauthorized
如果我们提供了正确的细节,那么我们可以看到我们的处理程序被调用,并且返回了正确的数据:
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"foo","password":"bar"}'
User Id 1
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"bar","password":"foo"}'
User Id 2
现在我们已经有了一个经过身份验证的用户,我们可以生成一个令牌,它可以用于我们的其余请求,而不是到处传递我们的用户名和密码。这通常被称为无记名代币,方便的是,有一个护照策略。
对于我们的代币,我们将使用一种叫做 JSON 网络代币 ( JWT )的东西。JWT 允许我们对 JSON 对象中的令牌进行编码,然后对它们进行解码和验证。存储在里面的数据是开放的,容易读取,所以密码不应该存储在里面;然而,这使得验证用户变得非常简单。我们还可以为这些令牌提供到期日期,这有助于限制暴露令牌的严重性。
你可以在 http://jwt.io/阅读更多关于 JWT 的信息。
我们可以使用以下命令安装 JWT:
[~/examples/example-19]$ npm install jsonwebtoken
一旦用户通过身份验证,我们就可以安全地为他们提供一个令牌,以便在未来的请求中使用:
var JSONWebToken = require( 'jsonwebtoken' ),
Crypto = require( 'crypto' );
var generateToken = function ( request, response ) {
// The payload just contains the id of the user
// and their username, we can verify whether the claim
// is correct using JSONWebToken.verify
var payload = {
id: user.id,
username: user.username
};
// Generate a random string
// Usually this would be an app wide constant
// But can be done both ways
var secret = Crypto.randomBytes( 128 )
.toString( 'base64' );
// Create the token with a payload and secret
var token = JSONWebToken.sign( payload, secret );
// The user is still referencing the same object
// in users, so no need to set it again
// If we were using a database, we would save
// it here
request.user.secret = secret
return token;
}
var generateTokenHandler = function ( request, response ) {
var user = request.user;
// Generate our token
var token = generateToken( user );
// Return the user a token to use
response.send( token );
};
app.post(
'/login',
Passport.authenticate( 'local', { session: false } ),
generateTokenHandler
);
现在,当用户登录时,他们将会看到一个令牌,我们可以对其进行验证。
让我们运行我们的节点服务器:
[~/examples/example-19]$ node server.js
Listening on port 8080
当我们现在登录时,我们会收到一个令牌:
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"foo","password":"bar"}'
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZC
I6MSwidXNlcm5hbWUiOiJmb28iLCJpYXQiOjE0MzcyO
TQ3OTV9.iOZO7oCIceZl6YvZqVP9WZLRx-XVvJFMF1p
pPCEsGGs
我们可以在http://jwt.io/把这个输入到调试器中,看到内容,如图所示:
{
"id": 1,
"username": "foo",
"iat": 1437294795
}
如果我们有这个秘密,我们就可以验证这个令牌是正确的。每当我们请求令牌时,签名都会改变:
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"foo","password":"bar"}'
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZC
I6MSwidXNlcm5hbWUiOiJmb28iLCJpYXQiOjE0MzcyO
TQ5OTl9.n1eRQVOM9qORTIMUpslH-ycTNEYdLDKa9lU
pmhf44s0
我们可以使用passport-bearer
认证用户;它的设置与passport-local
非常相似。但是,我们不接受来自主体的用户名和密码,而是接受不记名令牌;这可以使用查询字符串、正文或Authorization
头来传递:
首先我们必须安装passport-http-bearer
:
[~/examples/example-19]$ npm install passport-http-bearer
让我们创建我们的验证器。有两个步骤:第一是确保解码的信息与我们的用户匹配,这将是我们通常检索用户的地方;然后,“一旦我们有了用户并且该用户有效,我们就可以根据用户的秘密来检查令牌是否有效:
var BearerStrategy = require( 'passport-http-bearer' ).Strategy;
var verifyToken = function( token, done ) {
var payload = JSONWebToken.decode( token );
var user = users[ payload.username ];
// If we can't find a user, or the information
// doesn't match then return false
if ( user == null ||
user.id !== payload.id ||
user.username !== payload.username ) {
return done( null, false );
}
// Ensure the token is valid now we have a user
JSONWebToken.verify( token, user.secret, function ( error, decoded ) {
if ( error || decoded == null ) {
return done( error, false );
}
return done( null, user );
});
}
var bearerStrategy = new BearerStrategy(
verifyToken
)
我们可以将该策略注册为持有人,以便以后使用:
Passport.use( 'bearer', bearerStrategy );
我们可以创建一个简单的路由,从中检索经过身份验证的用户的详细信息:
app.get(
'/userinfo',
Passport.authenticate( 'bearer', { session: false } ),
function ( request, response ) {
var user = request.user;
response.send( {
id: user.id,
username: user.username
});
}
);
让我们运行 Node.js 服务器:
[~/examples/example-19]$ node server.js
Listening on port 8080
一旦我们收到令牌:
[~]$ curl -X POST http://localhost:8080/login \
-H 'Content-Type: application/json' \
-d '{"username":"foo","password":"bar"}'
我们可以在请求中使用结果:
[~]$ curl -X GET http://localhost:8080/userinfo \
-H 'Authorization: Bearer <token>'
{"id":1,"username":"foo"}
OAuth 提供了很多优势;例如,它不需要处理用户的实际身份。我们可以让用户使用他们信任的服务登录,如谷歌、脸书或 Auth0。
对于下面的例子,我将使用Auth0
。他们为你提供了一个免费的账号:https://auth0.com/。
您需要注册并创建一个api
(选择AngularJS + Node.js
),然后进入设置并记下域、客户端 id 和客户端密码。你需要这些来建立OAuth
。
我们可以使用passport-oauth2
使用 OAuth 进行身份验证:
[~/examples/example-19]$ npm install --save passport-oauth2
与我们的承载令牌一样,我们希望验证服务器返回的内容,这将是一个具有 id 的用户对象。我们会将其与数据中的用户进行匹配,或者创建一个新用户:
var validateOAuth = function ( accessToken, refreshToken, profile, done ) {
var keys = Object.keys( users ), user = null;
for( var iKey = 0; iKey < keys.length; i += 1 ) {
user = users[ key ];
if ( user.thirdPartyId !== profile.user_id ) { continue; }
return done( null, user );
}
users[ profile.name ] = user = {
username: profile.name,
id: keys.length,
thirdPartyId: profile.user_id
}
done( null, user );
};
一旦我们有了验证用户的功能,我们就可以为我们的 OAuth 策略组合选项:
var oAuthOptions = {
authorizationURL: 'https://<domain>.auth0.com/authorize',
tokenURL: 'https://<domain>.auth0.com/oauth/token',
clientID: '<client id>',
clientSecret: '<client secret>',
callbackURL: "http://localhost:8080/oauth/callback"
}
然后我们创建我们的策略,如下所示:
var OAuth2Strategy = require( 'passport-oauth2' ).Strategy;
oAuthStrategy = new OAuth2Strategy( oAuthOptions, validateOAuth );
在我们使用我们的策略之前,我们需要用我们自己的方法闪开类型的策略userProfile
,这样我们就可以请求用户对象在validateOAuth
中使用:
var parseUserProfile = function ( done, error, body ) {
if ( error ) {
return done( new Error( 'Failed to fetch user profile' ) )
}
var json;
try {
json = JSON.parse( body );
} catch ( error ) {
return done( error );
}
done( null, json );
}
var getUserProfile = function( accessToken, done ) {
oAuthStrategy._oauth2.get(
"https://<domain>.auth0.com/userinfo",
accessToken,
parseUserProfile.bind( null, done )
)
}
oAuthStrategy.userProfile = getUserProfile
我们可以将该策略注册为oauth
,以便以后使用:
Passport.use( 'oauth', oAuthStrategy );
我们需要创建两条路由来处理我们的 OAuth 身份验证:一条路由用于启动流,另一条路由用于标识服务器返回:
app.get( '/oauth', Passport.authenticate( 'oauth', { session: false } ) );
我们可以在这里使用我们的generateTokenHandler
,因为我们的请求会有一个用户在上面。
app.get( '/oauth/callback',
Passport.authenticate( 'oauth', { session: false } ),
generateTokenHandler
);
我们现在可以启动我们的服务器并请求http://localhost:8080/oauth
;服务器会将你重定向到Auth0
。登录后,您将收到一个可与/userinfo
一起使用的令牌。
如果您正在使用会话,您可以将用户保存到会话中,并将他们重定向回您的首页(或为登录用户设置的默认页面)。对于单页应用,当使用类似 Angular 这样的东西时,您可能希望使用 URL 中的令牌来重定向用户,以便客户端框架抓取并保存。
我们现在可以认证用户;这很好,因为我们现在可以弄清楚这些人是谁,然后将用户限制在特定的资源范围内。
在下一章中,我们将讨论调试,如果我们的用户没有通过身份验证,我们可能需要使用它。
Prepared for Bentham Chang, Safari ID bentham@gmail.com User number: 2843974 © 2015 Safari Books Online, LLC. This download file is made available for personal use only and is subject to the Terms of Service. Any other use requires prior written consent from the copyright owner. Unauthorized use, reproduction and/or distribution are strictly prohibited and violate applicable laws. All rights reserved.