使用什么身份验证策略?
What authentication strategy to use?
最近我一直在阅读有关 OAuth2、OpenID Connect 等的内容。但仍然不知道何时使用以及如何实现它。我现在正在考虑使用 NodeJS。
假设我想创建一个博客服务。此服务将公开 API 供客户使用。 "Clients" 包括管理 CMS。我认为分离我的服务器和客户端会很好 (UI)。我可以在不接触服务器的情况下更改 UI。这些客户端很可能是单页 Web 应用程序。
好的第一个问题:在这个例子中,我应该使用 OAuth2 吗?为什么?仅仅是因为我授权管理应用程序通过博客访问吗?
自从它的 SPA 以来,我认为正确的策略是 OAuth2 隐式流?
对于每个应用程序,例如。 admin cms,我将不得不生成一个传递给授权服务器的 AppID。不需要应用程序密码,对吗?
在这种情况下是否可以使用 google 登录(而不是 username/password)? OpenID 连接会这样做吗?
如何在 NodeJS 中实现所有这些?我看到 https://github.com/jaredhanson/oauth2orize,但我没有看到如何实现隐式流。
我确实看到了一个非官方示例 https://github.com/reneweb/oauth2orize_implicit_example/blob/master/app.js,但我在想为什么需要会话?我认为令牌的目标之一是让服务器可以无状态?
我也在想,什么时候用APIkey/secret认证呢?
根据我的经验,OAuth2 是保护 API 的标准方式。我建议使用 OpenID Connect,因为它向 OAuth2 的其他基于授权的规范添加了身份验证。您还可以在 "clients" 之间获得单点登录。
Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?
分离客户端和服务器是一个很好的概念(我通常也会这样做)但是,我建议改为使用授权代码流,因为它不会向浏览器公开令牌。阅读http://alexbilbie.com/2014/11/oauth-and-javascript/。改为使用瘦服务器端代理将令牌添加到请求中。不过,我通常会避免在客户端使用任何服务器生成的代码(例如 java 中的 JSP 或 rails 中的 erb/haml),因为它会将客户端与服务器过度耦合。
For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?
您需要一个客户端 ID 才能进行隐式流。如果您使用授权代码流(推荐),您将需要 ID 和秘密,但秘密将保存在瘦服务器端代理中,而不是仅客户端应用程序中(因为它不能是秘密的)例)
Is it possible to use google login in this case (instead of username/password)? Does OpenID connect do this?
是的。 Google 使用 openid 连接
How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.
openid connect 的一个好处是(如果您使用其他提供程序,如 google),您不必自己实现提供程序,您只需要编写客户端代码 (and/or 利用客户端库)。有关 nodejs,请参阅 http://openid.net/developers/libraries/ for different certified implementations. See https://www.npmjs.com/package/passport-openidconnect。
让我们检查一下您的问题
- 我应该使用 OAuth2 吗?为什么?
A:好吧,就像今天的旧 OpenId 2 authentication protocol has been marked as obsolete (November 2014) and OpenId Connect is an identity layer built on top of OAuth2 一样,真正的问题是了解和验证用户身份(身份验证部分)对您和您的企业是否重要。如果答案是“是”,那么选择 OpenId Connect,否则你可以选择两者中的任何一个,你觉得更舒服的那个。
- 既然是 SPA,我认为正确的策略是 OAuth2 隐式流程?
答:不是真的。您可以在使用 SPA 时实施任何策略,有些策略比其他策略需要更多的工作,并且在很大程度上取决于您想要完成的目标。隐式流程是最简单的,但它不会验证您的用户,因为访问令牌是直接颁发的。
When issuing an access token during the implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identity can be verified via the redirection URI used to deliver the access token to the client.
我不建议您的应用(或任何需要适当安全级别的应用1)使用此流程。
如果你想保持简单,你应该使用 Resource Owner Grant flow with an username and password but again there is nothing that prevents you of implementing the Authorization Code Grant 流程,特别是如果你想让第三方应用程序使用你的服务(在我看来这是一个成功的策略)并且它会相对更多比其他方法更安全,因为它需要用户的明确同意。
- 对于每个应用程序,例如。 admin cms,我将不得不生成一个传递给授权服务器的 AppID。不需要应用密码,对吗?
A:是的,这是正确的,但当您不能使用基本身份验证时,client_secret 可用于为资源所有者流程中的令牌端点添加额外的安全层,这不是必需的在任何其他流程中。2 3
The authorization server MUST:
require client authentication for confidential clients or for any
client that was issued client credentials (or with other
authentication requirements),
authenticate the client if client authentication is included, and
validate the resource owner password credentials using its
existing password validation algorithm.
和
Alternatively, the authorization server MAY support including the client credentials in the request-body (...) Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes)
- 在这种情况下是否可以使用 google 登录(而不是 username/password)? OpenID 连接是否执行此操作?
A:是的,可以使用 google 登录,在这种情况下,您只是将身份验证和授权工作委托给 google 服务器。使用授权服务器的好处之一是能够通过单一登录访问其他资源,而无需为您要访问的每个资源创建本地帐户。
- 如何在 NodeJS 中实现所有这些?
嗯,你是从右脚开始的。使用 oaut2horize is the most simple way to implement an authorization server to issue tokens. All other libraries I tested were too complicated of use and integrate with node and express (disclaimer: this is just my opinion). OAuthorize plays nicely with passport.js(both from the same author) which is a great framework to enforce the authentication and authorization with over 300+ strategies like google, facebook, github, etc. You can easily integrate google using passport-google(obsolete), passport-google-oauth and passport-google-plus.
让我们举个例子
storage.js
// An array to store our clients. You should likely store this in a
// in-memory storage mechanism like Redis
// you should generate one of this for any of your api consumers
var clients = [
{id: 'as34sHWs34'}
// can include additional info like:
// client_secret or password
// redirect uri from which client calls are expected to originate
];
// An array to store our tokens. Like the clients this should go in a memory storage
var tokens = [];
// Authorization codes storage. Those will be exchanged for tokens at the end of the flow.
// Should be persisted in memory as well for fast access.
var codes = [];
module.exports = {
clients: clients,
tokens: tokens,
codes: codes
};
oauth.js
// Sample implementation of Authorization Code Grant
var oauth2orize = require('oauth2orize');
var _ = require('lodash');
var storage = require('./storage');
// Create an authorization server
var server = oauth2orize.createServer();
// multiple http request responses will be used in the authorization process
// so we need to store the client_id in the session
// to later restore it from storage using only the id
server.serializeClient(function (client, done) {
// return no error so the flow can continue and pass the client_id.
return done(null, client.id);
});
// here we restore from storage the client serialized in the session
// to continue negotiation
server.deserializeClient(function (id, done) {
// return no error and pass a full client from the serialized client_id
return done(null, _.find(clients, {id: id}));
});
// this is the logic that will handle step A of oauth 2 flow
// this function will be invoked when the client try to access the authorization endpoint
server.grant(oauth2orize.grant.code(function (client, redirectURI, user, ares, done) {
// you should generate this code any way you want but following the spec
// https://www.rfc-editor.org/rfc/rfc6749#appendix-A.11
var generatedGrantCode = uid(16);
// this is the data we store in memory to use in comparisons later in the flow
var authCode = {code: generatedGrantCode, client_id: client.id, uri: redirectURI, user_id: user.id};
// store the code in memory for later retrieval
codes.push(authCode);
// and invoke the callback with the code to send it to the client
// this is where step B of the oauth2 flow takes place.
// to deny access invoke an error with done(error);
// to grant access invoke with done(null, code);
done(null, generatedGrantCode);
}));
// Step C is initiated by the user-agent(eg. the browser)
// This is step D and E of the oauth2 flow
// where we exchange a code for a token
server.exchange(oauth2orize.exchange.code(function (client, code, redirectURI, done) {
var authCode = _.find(codes, {code: code});
// if the code presented is not found return an error or false to deny access
if (!authCode) {
return done(false);
}
// if the client_id from the current request is not the same that the previous to obtain the code
// return false to deny access
if (client.id !== authCode.client_id) {
return done(null, false);
}
// if the uris from step C and E are not the same deny access
if (redirectURI !== authCode.uri) {
return done(null, false);
}
// generate a new token
var generatedTokenCode = uid(256);
var token = {token: generatedTokenCode, user_id: authCode.user_id, client_id: authCode.client_id};
tokens.push(token);
// end the flow in the server by returning a token to the client
done(null, token);
}));
// Sample utility function to generate tokens and grant codes.
// Taken from oauth2orize samples
function uid(len) {
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var buf = []
, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
, charlen = chars.length;
for (var i = 0; i < len; ++i) {
buf.push(chars[getRandomInt(0, charlen - 1)]);
}
return buf.join('');
}
module.exports = server;
app.js
var express = require('express');
var passport = require('passport');
var AuthorizationError = require('oauth2orize').AuthorizationError;
var login = require('connect-ensure-login');
var storage = require('./storage');
var _ = require('lodash');
app = express();
var server = require('./oauthserver');
// ... all the standard express configuration
app.use(express.session({ secret: 'secret code' }));
app.use(passport.initialize());
app.use(passport.session());
app.get('/oauth/authorize',
login.ensureLoggedIn(),
server.authorization(function(clientID, redirectURI, done) {
var client = _.find(storage.clients, {id: clientID});
if (client) {
return done(null, client, redirectURI);
} else {
return done(new AuthorizationError('Access denied'));
}
}),
function(req, res){
res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client });
});
app.post('/oauth/authorize/decision',
login.ensureLoggedIn(),
server.decision()
);
app.post('/oauth/token',
passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
server.token(),
server.errorHandler()
);
- (...) 但我在想的是为什么需要 sessions?我认为令牌的目标之一是让服务器可以无状态?
When a client redirects a user to user authorization endpoint, an authorization transaction is initiated. To complete the transaction, the user must authenticate and approve the authorization request. Because this may involve multiple HTTP request/response exchanges, the transaction is stored in the session.
是的,但是 session 用于令牌协商过程。稍后您强制授权在授权 header 中发送令牌以使用获得的令牌授权每个请求。
最近我一直在阅读有关 OAuth2、OpenID Connect 等的内容。但仍然不知道何时使用以及如何实现它。我现在正在考虑使用 NodeJS。
假设我想创建一个博客服务。此服务将公开 API 供客户使用。 "Clients" 包括管理 CMS。我认为分离我的服务器和客户端会很好 (UI)。我可以在不接触服务器的情况下更改 UI。这些客户端很可能是单页 Web 应用程序。
好的第一个问题:在这个例子中,我应该使用 OAuth2 吗?为什么?仅仅是因为我授权管理应用程序通过博客访问吗?
自从它的 SPA 以来,我认为正确的策略是 OAuth2 隐式流?
对于每个应用程序,例如。 admin cms,我将不得不生成一个传递给授权服务器的 AppID。不需要应用程序密码,对吗?
在这种情况下是否可以使用 google 登录(而不是 username/password)? OpenID 连接会这样做吗?
如何在 NodeJS 中实现所有这些?我看到 https://github.com/jaredhanson/oauth2orize,但我没有看到如何实现隐式流。
我确实看到了一个非官方示例 https://github.com/reneweb/oauth2orize_implicit_example/blob/master/app.js,但我在想为什么需要会话?我认为令牌的目标之一是让服务器可以无状态?
我也在想,什么时候用APIkey/secret认证呢?
根据我的经验,OAuth2 是保护 API 的标准方式。我建议使用 OpenID Connect,因为它向 OAuth2 的其他基于授权的规范添加了身份验证。您还可以在 "clients" 之间获得单点登录。
Since its SPA's, I think the right strategy is OAuth2 Implicit Flow?
分离客户端和服务器是一个很好的概念(我通常也会这样做)但是,我建议改为使用授权代码流,因为它不会向浏览器公开令牌。阅读http://alexbilbie.com/2014/11/oauth-and-javascript/。改为使用瘦服务器端代理将令牌添加到请求中。不过,我通常会避免在客户端使用任何服务器生成的代码(例如 java 中的 JSP 或 rails 中的 erb/haml),因为它会将客户端与服务器过度耦合。
For each app, eg. admin cms, I will have to generate an AppID which is passed to the auth server. No app secret is required correct?
您需要一个客户端 ID 才能进行隐式流。如果您使用授权代码流(推荐),您将需要 ID 和秘密,但秘密将保存在瘦服务器端代理中,而不是仅客户端应用程序中(因为它不能是秘密的)例)
Is it possible to use google login in this case (instead of username/password)? Does OpenID connect do this?
是的。 Google 使用 openid 连接
How do I implement all these in NodeJS? I see https://github.com/jaredhanson/oauth2orize, but I do not see how to implement the implicit flow.
openid connect 的一个好处是(如果您使用其他提供程序,如 google),您不必自己实现提供程序,您只需要编写客户端代码 (and/or 利用客户端库)。有关 nodejs,请参阅 http://openid.net/developers/libraries/ for different certified implementations. See https://www.npmjs.com/package/passport-openidconnect。
让我们检查一下您的问题
- 我应该使用 OAuth2 吗?为什么?
A:好吧,就像今天的旧 OpenId 2 authentication protocol has been marked as obsolete (November 2014) and OpenId Connect is an identity layer built on top of OAuth2 一样,真正的问题是了解和验证用户身份(身份验证部分)对您和您的企业是否重要。如果答案是“是”,那么选择 OpenId Connect,否则你可以选择两者中的任何一个,你觉得更舒服的那个。
- 既然是 SPA,我认为正确的策略是 OAuth2 隐式流程?
答:不是真的。您可以在使用 SPA 时实施任何策略,有些策略比其他策略需要更多的工作,并且在很大程度上取决于您想要完成的目标。隐式流程是最简单的,但它不会验证您的用户,因为访问令牌是直接颁发的。
When issuing an access token during the implicit grant flow, the authorization server does not authenticate the client. In some cases, the client identity can be verified via the redirection URI used to deliver the access token to the client.
我不建议您的应用(或任何需要适当安全级别的应用1)使用此流程。
如果你想保持简单,你应该使用 Resource Owner Grant flow with an username and password but again there is nothing that prevents you of implementing the Authorization Code Grant 流程,特别是如果你想让第三方应用程序使用你的服务(在我看来这是一个成功的策略)并且它会相对更多比其他方法更安全,因为它需要用户的明确同意。
- 对于每个应用程序,例如。 admin cms,我将不得不生成一个传递给授权服务器的 AppID。不需要应用密码,对吗?
A:是的,这是正确的,但当您不能使用基本身份验证时,client_secret 可用于为资源所有者流程中的令牌端点添加额外的安全层,这不是必需的在任何其他流程中。2 3
The authorization server MUST:
require client authentication for confidential clients or for any client that was issued client credentials (or with other authentication requirements),
authenticate the client if client authentication is included, and
validate the resource owner password credentials using its existing password validation algorithm.
和
Alternatively, the authorization server MAY support including the client credentials in the request-body (...) Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes)
- 在这种情况下是否可以使用 google 登录(而不是 username/password)? OpenID 连接是否执行此操作?
A:是的,可以使用 google 登录,在这种情况下,您只是将身份验证和授权工作委托给 google 服务器。使用授权服务器的好处之一是能够通过单一登录访问其他资源,而无需为您要访问的每个资源创建本地帐户。
- 如何在 NodeJS 中实现所有这些?
嗯,你是从右脚开始的。使用 oaut2horize is the most simple way to implement an authorization server to issue tokens. All other libraries I tested were too complicated of use and integrate with node and express (disclaimer: this is just my opinion). OAuthorize plays nicely with passport.js(both from the same author) which is a great framework to enforce the authentication and authorization with over 300+ strategies like google, facebook, github, etc. You can easily integrate google using passport-google(obsolete), passport-google-oauth and passport-google-plus.
让我们举个例子
storage.js
// An array to store our clients. You should likely store this in a
// in-memory storage mechanism like Redis
// you should generate one of this for any of your api consumers
var clients = [
{id: 'as34sHWs34'}
// can include additional info like:
// client_secret or password
// redirect uri from which client calls are expected to originate
];
// An array to store our tokens. Like the clients this should go in a memory storage
var tokens = [];
// Authorization codes storage. Those will be exchanged for tokens at the end of the flow.
// Should be persisted in memory as well for fast access.
var codes = [];
module.exports = {
clients: clients,
tokens: tokens,
codes: codes
};
oauth.js
// Sample implementation of Authorization Code Grant
var oauth2orize = require('oauth2orize');
var _ = require('lodash');
var storage = require('./storage');
// Create an authorization server
var server = oauth2orize.createServer();
// multiple http request responses will be used in the authorization process
// so we need to store the client_id in the session
// to later restore it from storage using only the id
server.serializeClient(function (client, done) {
// return no error so the flow can continue and pass the client_id.
return done(null, client.id);
});
// here we restore from storage the client serialized in the session
// to continue negotiation
server.deserializeClient(function (id, done) {
// return no error and pass a full client from the serialized client_id
return done(null, _.find(clients, {id: id}));
});
// this is the logic that will handle step A of oauth 2 flow
// this function will be invoked when the client try to access the authorization endpoint
server.grant(oauth2orize.grant.code(function (client, redirectURI, user, ares, done) {
// you should generate this code any way you want but following the spec
// https://www.rfc-editor.org/rfc/rfc6749#appendix-A.11
var generatedGrantCode = uid(16);
// this is the data we store in memory to use in comparisons later in the flow
var authCode = {code: generatedGrantCode, client_id: client.id, uri: redirectURI, user_id: user.id};
// store the code in memory for later retrieval
codes.push(authCode);
// and invoke the callback with the code to send it to the client
// this is where step B of the oauth2 flow takes place.
// to deny access invoke an error with done(error);
// to grant access invoke with done(null, code);
done(null, generatedGrantCode);
}));
// Step C is initiated by the user-agent(eg. the browser)
// This is step D and E of the oauth2 flow
// where we exchange a code for a token
server.exchange(oauth2orize.exchange.code(function (client, code, redirectURI, done) {
var authCode = _.find(codes, {code: code});
// if the code presented is not found return an error or false to deny access
if (!authCode) {
return done(false);
}
// if the client_id from the current request is not the same that the previous to obtain the code
// return false to deny access
if (client.id !== authCode.client_id) {
return done(null, false);
}
// if the uris from step C and E are not the same deny access
if (redirectURI !== authCode.uri) {
return done(null, false);
}
// generate a new token
var generatedTokenCode = uid(256);
var token = {token: generatedTokenCode, user_id: authCode.user_id, client_id: authCode.client_id};
tokens.push(token);
// end the flow in the server by returning a token to the client
done(null, token);
}));
// Sample utility function to generate tokens and grant codes.
// Taken from oauth2orize samples
function uid(len) {
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var buf = []
, chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
, charlen = chars.length;
for (var i = 0; i < len; ++i) {
buf.push(chars[getRandomInt(0, charlen - 1)]);
}
return buf.join('');
}
module.exports = server;
app.js
var express = require('express');
var passport = require('passport');
var AuthorizationError = require('oauth2orize').AuthorizationError;
var login = require('connect-ensure-login');
var storage = require('./storage');
var _ = require('lodash');
app = express();
var server = require('./oauthserver');
// ... all the standard express configuration
app.use(express.session({ secret: 'secret code' }));
app.use(passport.initialize());
app.use(passport.session());
app.get('/oauth/authorize',
login.ensureLoggedIn(),
server.authorization(function(clientID, redirectURI, done) {
var client = _.find(storage.clients, {id: clientID});
if (client) {
return done(null, client, redirectURI);
} else {
return done(new AuthorizationError('Access denied'));
}
}),
function(req, res){
res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client });
});
app.post('/oauth/authorize/decision',
login.ensureLoggedIn(),
server.decision()
);
app.post('/oauth/token',
passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
server.token(),
server.errorHandler()
);
- (...) 但我在想的是为什么需要 sessions?我认为令牌的目标之一是让服务器可以无状态?
When a client redirects a user to user authorization endpoint, an authorization transaction is initiated. To complete the transaction, the user must authenticate and approve the authorization request. Because this may involve multiple HTTP request/response exchanges, the transaction is stored in the session.
是的,但是 session 用于令牌协商过程。稍后您强制授权在授权 header 中发送令牌以使用获得的令牌授权每个请求。