使用什么身份验证策略?

Jie*_*eng 7 authentication node.js openid-connect oauth2

最近我一直在阅读关于OAuth2,OpenID Connect等的内容.但是在使用什么时以及如何实现它时仍然非常迷失.我正在考虑使用NodeJS.

让我们说我想创建一个博客服务.此服务将公开API供客户使用."客户端"包括管理CMS.我认为将服务器和客户端(UI)分离会很好.我可以在不触及服务器的情况下更改UI.这些客户端可能是单页Web应用程序.

好的第一个问题:在这个例子中,我应该使用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​​密钥/秘密身份验证?

dev*_*ept 14

让我们检查一下你的问题

  1. 我应该使用OAuth2吗?为什么?

    答:嗯,今天老的OpenID 2身份验证协议已被标记为过时(2014年11月)和ID连接一种身份层建立在顶部的OAuth2所以真正的问题是,如果是您和您的企业必须了解和验证您的用户的身份(身份验证部分).如果答案为"是",那么请选择OpenId Connect,否则您可以选择其中任何一个,您觉得更舒服.

  2. 自SPA以来,我认为正确的策略是OAuth2 Implicit Flow?

    答:不是.您可以在使用SPA时实施任何策略,有些工作比其他工作更多,并且很大程度上取决于您要完成的任务.隐式流是最简单的,但它不会对您的用户进行身份验证,因为直接发出访问令牌.

    在隐式授权流期间发出访问令牌时,授权服务器不会对客户端进行身份验证.在某些情况下,可以通过用于将访问令牌传递到客户端的重定向URI来验证客户端身份.

    我不建议您的应用程序(或任何需要相当级别的安全性的应用程序1)的流程.

    如果你想保持简单,你应该使用资源所有者授权流程和用户名和密码,但是再没有什么能阻止你实施授权代码授权流程,特别是如果你想允许第三方应用程序使用你的服务(在我的意见是一个成功的策略),它将比其他人更安全,因为它需要用户的明确同意.

  3. 对于每个应用程序,例如.admin cms,我将生成一个传递给auth服务器的AppID.没有应用秘密是否正确?

    答:是的,但是当您无法使用基本身份验证时,client_secret可用于向资源所有者流中的令牌端点添加额外的安全层,这在任何其他流中都不是必需的.2 3

    授权服务器必须:

    • 要求对机密客户端或任何已发布客户端凭据(或其他身份验证要求)的客户端进行客户端身份验证,

    • 如果包含客户端身份验证,则对客户端进行身份验证

    • 使用其现有的密码验证算法验证资源所有者密码凭据.

    可替换地,授权服务器可以支持包括在请求主体(...)包括在使用两个参数的请求体的客户机凭证的客户机凭证是不推荐的,并且应该被限制到客户端无法直接利用HTTP基本身份验证方案(或其他基于密码的HTTP身份验证方案)

  4. 在这种情况下是否可以使用谷歌登录(而不是用户名/密码)?OpenID连接是否可以这样做?

    答:是的,可以使用谷歌登录,在这种情况下,您只是将身份验证和授权作业委派给谷歌服务器.使用授权服务器的一个好处是能够通过单一登录访问其他资源,而无需为要访问的每个资源创建本地帐户.

  5. 如何在NodeJS中实现所有这些?

    那么你是从右脚开始的.使用oaut2horize是实现授权服务器以发出令牌的最简单方法.我测试的所有其他库都太复杂了,并且与节点和表达集成(免责声明:这只是我的观点).OAuthorize与passport.js(均来自同一作者)很好地配合,这是一个很好的框架,可以使用google,facebook,github等300多种策略强制执行身份验证和授权.您可以使用passport-google轻松集成google(已过时) ),passport-google-oauthpassport-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
    };
    
    Run Code Online (Sandbox Code Playgroud)

    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;
    
    Run Code Online (Sandbox Code Playgroud)

    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()
    );
    
    Run Code Online (Sandbox Code Playgroud)
  6. (...)但我在想的是为什么需要会议?我认为令牌的目标之一是服务器可以无国籍?

    当客户端将用户重定向到用户授权端点时,将启动授权事务.要完成交易,用户必须验证并批准授权请求.因为这可能涉及多个HTTP请求/响应交换,所以事务存储在会话中.

    是的,但是会话用于令牌协商过程.稍后,您将强制授权在Authorization标头中发送令牌,以使用获取的令牌授权每个请求.