让我们检查您的问题
- 我应该使用 OAuth2 吗?为什么?
答:好吧,因为今天旧的OpenId 2身份验证协议已被标记为过时(2014 年 11 月),而OpenId Connect是建立在OAuth2之上的身份层,所以真正的问题是了解和验证对您和您的企业是否重要您的用户的身份(身份验证部分)。如果答案是“是”,则选择 OpenId Connect,否则您可以选择两者中的任何一个,您觉得更舒服的那个。
- 自 SPA 以来,我认为正确的策略是 OAuth2 隐式流?
答:没有。您可以在使用 SPA 时实施任何策略,有些策略比其他策略需要更多的工作,并且很大程度上取决于您要完成的工作。隐式流程是最简单的,但它不会对您的用户进行身份验证,因为访问令牌是直接发出的。
在隐式授权流程期间发布访问令牌时,授权服务器不会对客户端进行身份验证。在某些情况下,可以通过用于将访问令牌传递给客户端的重定向 URI 来验证客户端身份。
我不会为您的应用程序(或任何需要良好安全级别1的应用程序)推荐此流程。
如果您想保持简单,您应该使用带有用户名和密码的资源所有者授予流程,但同样没有什么可以阻止您实施授权代码授予流程,特别是如果您希望允许第三方应用程序使用您的服务(在我的观点是一个成功的策略)并且它会比其他的更安全,因为它需要用户的明确同意。
- 对于每个应用程序,例如。admin cms,我将不得不生成一个传递给身份验证服务器的 AppID。不需要应用程序密码对吗?
答:是的,这是正确的,但是当您无法使用基本身份验证时,可以使用 client_secret 为资源所有者流程中的令牌端点添加额外的安全层,这在任何其他流程中都不需要。2 3
授权服务器必须:
要求对机密客户端或任何已获得客户端凭据(或具有其他身份验证要求)的客户端进行客户端身份验证,
如果包括客户端身份验证,则对客户端进行身份验证,并且
使用其现有的密码验证算法验证资源所有者的密码凭据。
和
或者,授权服务器可以支持在请求正文中包含客户端凭据 (...) 不推荐使用这两个参数在请求正文中包含客户端凭据,并且应该仅限于无法直接使用 HTTP Basic 的客户端身份验证方案(或其他基于密码的 HTTP 身份验证方案)
- 在这种情况下是否可以使用谷歌登录(而不是用户名/密码)?OpenID 连接会这样做吗?
答:是的,可以使用谷歌登录,在这种情况下,您只是将身份验证和授权工作委托给谷歌服务器。使用授权服务器的好处之一是能够通过一次登录来访问其他资源,而无需为要访问的每个资源创建本地帐户。
- 我如何在 NodeJS 中实现所有这些?
好吧,你从右脚开始。使用oaut2horize是实现授权服务器颁发令牌的最简单方法。我测试的所有其他库的使用都过于复杂,并且与 node 和 express 集成(免责声明:这只是我的观点)。OAuthorize 与passport.js(均来自同一作者)很好地配合使用,这是一个很好的框架,可以通过 300 多种策略(如 google、facebook、github 等)强制执行身份验证和授权。您可以使用passport-google 轻松集成 google(已过时)、passport-google-oauth和passport-google-plus。
我们来看例子
存储.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;
应用程序.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 标头中发送令牌,以使用获得的令牌授权每个请求。