最近我一直在阅读关于OAuth2,OpenID Connect等的内容。但是在使用什么时以及如何实现它时仍然非常迷失。我正在考虑使用NodeJS。
让我们说我想创建一个博客服务。此服务将公开API供客户使用。 “客户端”包括管理CMS。我认为将服务器和客户端(UI)分离会很好。我可以在不触及服务器的情况下更改UI。这些客户端可能是单页Web应用程序。
Ok第一个问题:在这个例子中,我应该使用OAuth2吗?为什么?是因为我授权管理员应用程序通过博客访问?
自SPA以来,我认为正确的策略是OAuth2 Implicit Flow?
对于每个应用,例如。 admin cms,我将生成一个传递给auth服务器的AppID。没有应用秘密是否正确?
在这种情况下是否可以使用谷歌登录(而不是用户名/密码)? OpenID连接是否可以这样做?
如何在NodeJS中实现所有这些功能?我看到https://github.com/jaredhanson/oauth2orize,但我没有看到如何实现隐式流程。
我确实看到了一个非官方的例子https://github.com/reneweb/oauth2orize_implicit_example/blob/master/app.js,但我在想的是为什么需要会话?我认为令牌的目标之一是服务器可以无国籍?
我也想知道,我什么时候应该使用API密钥/秘密身份验证?
答案 0 :(得分:13)
让我们检查一下你的问题
我应该使用OAuth2吗?为什么呢?
答:嗯,今天旧的OpenId 2身份验证协议已被标记为已过时(2014年11月),而OpenId Connect是身份层,建立在{{ 3}}所以真正的问题是,对您和您的企业来说,了解和验证用户身份(身份验证部分)是否很重要。如果答案是"是"然后去OpenId Connect,否则你可以选择其中任何一个,你感觉更舒服。
自SPA以来,我认为正确的策略是OAuth2 Implicit Flow?
答:不是。您可以在使用SPA时实施任何策略,有些工作比其他工作更多,并且很大程度上取决于您要完成的任务。隐式流是最简单的但它不会对您的用户进行身份验证,因为直接发出访问令牌。
在隐式授权流期间发出访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于将访问令牌传递给客户端的重定向URI来验证客户端身份。
我不建议您的应用(或任何需要相当安全级别OAuth2的应用)的此流程。
如果你想保持简单,你应该使用带有用户名和密码的1流,但是再一次没有什么可以阻止你实现Resource Owner Grant流,特别是如果你想允许第三方应用使用您的服务(我认为这是一个成功的策略),它将比其他人更安全,因为它需要用户的明确同意。
对于每个应用,例如。 admin cms,我将生成一个传递给auth服务器的AppID。没有应用秘密是否正确?
答:是的,但是当您无法使用基本身份验证时,client_secret可用于向资源所有者流中的令牌端点添加额外的安全层,这在任何其他流程中都不是必需的。Authorization Code Grant 2
授权服务器必须:
要求对机密客户端或任何客户端进行身份验证 已发布客户端凭据的客户端(或与其他客户端 认证要求),
- 对客户端进行身份验证
如果包含客户端身份验证,则
使用其验证资源所有者密码凭据 现有的密码验证算法。
和
或者,授权服务器可以支持在请求体中包含客户端凭证(...)在请求体中包含客户端凭证使用这两个参数是不推荐的,并且应该仅限于无法直接利用的客户端HTTP基本身份验证方案(或其他基于密码的HTTP身份验证方案)
在这种情况下是否可以使用谷歌登录(而不是用户名/密码)? OpenID连接是否可以这样做?
<答>答:是的,可以使用谷歌登录,在这种情况下,您只是将身份验证和授权作业委派给谷歌服务器。使用授权服务器的一个好处是能够通过单一登录访问其他资源,而无需为您要访问的每个资源创建本地帐户。如何在NodeJS中实现所有这些?
嗯,你是从右脚开始的。使用3是实现授权服务器以发出令牌的最简单方法。我测试的所有其他库都太复杂了,并且与节点和表达集成(免责声明:这只是我的观点)。 OAuthorize与oaut2horize(来自同一作者)很好地配合使用,这是一个很好的框架,可以使用google,facebook,github等300多种策略来强制执行身份验证和授权。您可以使用{{3 (已废弃),passport.js和passport-google。
让我们举个例子
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
// http://tools.ietf.org/html/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()
);
(...)但我在想的是为什么需要会议?我认为令牌的目标之一是服务器可以无国籍?
当客户端将用户重定向到用户授权端点时,将启动授权事务。要完成交易,用户必须验证并批准授权请求。因为这可能涉及多个HTTP请求/响应交换,所以事务存储在会话中。
是的,但是会话用于令牌协商过程。稍后,您将强制授权在Authorization标头中发送令牌,以使用获取的令牌授权每个请求。
答案 1 :(得分:2)
根据我的经验,OAuth2是保护API的标准方法。我建议使用OpenID Connect,因为它会在OAuth2的基于授权的规范中添加身份验证。您还可以在&#34;客户端&#34;之间进行单点登录。
自SPA以来,我认为正确的策略是OAuth2 Implicit Flow?
解耦客户端和服务器是一个很好的概念(我通常也会这样做)但是,我推荐授权代码流,因为它没有公开令牌到浏览器。阅读http://alexbilbie.com/2014/11/oauth-and-javascript/。请使用瘦服务器端代理将令牌添加到请求中。尽管如此,我通常会避免在客户端上使用任何服务器生成的代码(例如java中的JSP或rails中的erb / haml),因为它将客户端与服务器结合得太多了。
对于每个应用,例如。 admin cms,我将生成一个传递给auth服务器的AppID。没有应用秘密是否正确?
您需要隐式流的客户端ID。如果您使用授权代码流(推荐),您将需要一个ID和秘密,但秘密将保留在瘦服务器端代理中,而不是仅限客户端的应用程序(因为它可以&#t; t在这种情况下是秘密的)
在这种情况下是否可以使用谷歌登录(而不是用户名/密码)? OpenID连接是否可以这样做?
是。 Google使用openid connect
如何在NodeJS中实现所有这些功能?我看到https://github.com/jaredhanson/oauth2orize,但我看不到如何实现隐式流程。
关于openid connect的一个好处是(如果你使用像谷歌这样的其他提供商),你不必自己实现提供者,你只需要编写客户端代码(和/或利用客户图书馆)。有关不同的认证实施,请参阅http://openid.net/developers/libraries/。有关nodejs,请参阅https://www.npmjs.com/package/passport-openidconnect。