使用什么身份验证策略?

时间:2016-03-26 04:32:35

标签: node.js authentication openid-connect oauth2

最近我一直在阅读关于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​​密钥/秘密身份验证?

2 个答案:

答案 0 :(得分:13)

让我们检查一下你的问题

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

    答:嗯,今天旧的OpenId 2身份验证协议已被标记为已过时(2014年11月),而OpenId Connect身份层,建立在{{ 3}}所以真正的问题是,对您和您的企业来说,了解和验证用户身份(身份验证部分)是否很重要。如果答案是"是"然后去OpenId Connect,否则你可以选择其中任何一个,你感觉更舒服。

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

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

      

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

    我不建议您的应用(或任何需要相当安全级别OAuth2的应用)的此流程。

    如果你想保持简单,你应该使用带有用户名和密码的1流,但是再一次没有什么可以阻止你实现Resource Owner Grant流,特别是如果你想允许第三方应用使用您的服务(我认为这是一个成功的策略),它将比其他人更安全,因为它需要用户的明确同意。

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

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

      

    授权服务器必须:

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

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

    • 对客户端进行身份验证   
    • 使用其验证资源所有者密码凭据    现有的密码验证算法。

    •   

      

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

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

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

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

    嗯,你是从右脚开始的。使用3是实现授权服务器以发出令牌的最简单方法。我测试的所有其他库都太复杂了,并且与节点和表达集成(免责声明:这只是我的观点)。 OAuthorize与oaut2horize(来自同一作者)很好地配合使用,这是一个很好的框架,可以使用google,facebook,github等300多种策略来强制执行身份验证和授权。您可以使用{{3 (已废弃),passport.jspassport-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()
    );
    
  6. (...)但我在想的是为什么需要会议?我认为令牌的目标之一是服务器可以无国籍?

      

    当客户端将用户重定向到用户授权端点时,将启动授权事务。要完成交易,用户必须验证并批准授权请求。因为这可能涉及多个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