使用Angular.js和iOS客户端进行Node.js应用程序的验证

问题描述:

尽管我试过尽可能多地阅读不同的答案和帖子,但仍然无法完全满足我的需求。我试图找出处理用户身份验证,登录等最好(最有效但最安全)的方法。我有一个运行在Express上的Node.js服务器;我有一个Angular.js网络应用程序;我有一个iOS应用程序。我用Express/Node.js公开了一个RESTful API。使用Angular.js和iOS客户端进行Node.js应用程序的验证

饼干

的第一件事情我看书上说使用cookie,并存储在服务器端的会话ID /登录令牌(哈希)和客户端(非散列)。客户端会在每个请求中传输此ID,服务器会对其进行散列,解析并相应地处理请求。这不会感觉RESTful(不是一个大问题),但更重要的是,我需要复制我的API:一个用于用户名/密码认证(例如通过curl完成),一个用于基于cookie的认证(例如我的web应用程序)?

另一个问题是:如果我有一个用户有多个连接,他们使用两种浏览器(iPhone和iPad)登录。我的存储会议id是否需要现在是一个数组?

HTTP基本验证

接下来的想法是使用HTTP基本身份验证(使用SSL),这似乎很容易,但不建议,因为你需要每个请求传输用户名和密码。如果我使用HTTP Basic Auth来执行此操作,那么我会将用户名和密码存储在Cookie(或HTML本地存储)中以允许“记住我”功能吗?或者我可以将两者结合起来:对实际请求使用HTTP基本身份验证(发布新帖子等),并仅使用存储在cookie中的会话标识符来记录初始日志/记住我的方面?

正在传输会话ID比传输用户密码更安全吗?怎么样? 会话ID将会表面上看起来像是一个密码,所以对我来说,发送密码与发送密码会有相同的安全问题。

基本身份验证似乎在所有平台上都受支持,这非常理想。主要的缺点似乎是需要在每个请求中传输客户端身份验证数据。有没有办法缓解这个问题?

OAuth

OAuth似乎对我的需求过度杀伤。我想我会失去执行curl命令来测试我的API的能力。 OAuth如何改进cookie方法?你可能会说,我对可用的各种信息有些困惑,所以如果你有一套适用于这种情况的好链接,我很乐意阅读它们。我试图找到适合所有平台的解决方案,但仍尽可能安全。另外,如果我有任何我的术语错误,请纠正我,因为它会使搜索更容易。

谢谢。

更新:

我一直在想这个问题,我有一个想法。请告诉我,如果这是愚蠢的/不安全的/任何反馈,因为我不知道它是否好。

当用户登录时,我们生成一个随机会话ID(盐渍等)。这个可选的会话ID被发送到客户端,客户端可以存储(例如在cookie中),如果他们选择的话;会话ID存储在数据库中。

这个会话ID然后任选与每个请求的是HTTP认证头或查询字符串,或客户端,如果他们想可以只发送用户名和密码(这为我们提供了我们的正常REST API)发送。在服务器端,我们首先检查会话ID参数,如果它不存在,我们检查用户名/密码。如果两者都不存在 - 错误。

在服务器上,我们检查会话ID是否与正确的用户名关联。如果是,我们完成请求。

每次用户登录时,我们都会创建一个新的会话ID或删除当前的会话,并将其发送给登录请求的响应。

我认为这可以让我在适当的情况下使用常规REST API和基本身份验证,并保持会话/记住我的功能。它并没有解决多个登录问题,但否则我认为这种方式应该会。请告诉我。

+3

你有没有看过passport.js? http://passportjs.org/ –

+1

@MWay谢谢你。我一定会进一步研究它。您能否对我在“更新”部分发布的解决方案的安全性发表评论?如果它不可行,我想我可能会使用护照。 – matthewpalmer

+0

@matt,不需要做这个session_ids永恒...不要将它存储在数据库中,只能存储在缓存中...我们将这个标记存储在redis中,使用24h ttl并在每个用户查询中更新这个ttl。 ..如果用户沉默了24小时这个令牌过期了...... –

我会使用基于令牌的认证,您可以在每个请求中自动发送令牌。您必须登录一次,服务器将为您提供一个令牌,然后您可以使用该令牌随每个请求一起发送。该令牌将被添加到HTML标头中,以便您不必修改每个请求到浏览器。

您可以在API中设置某些调用,以便它们始终需要令牌,而其他调用可能不受令牌保护。

对于Express,您可以用快递,智威汤逊(https://www.npmjs.org/package/express-jwt

var expressJwt = require('express-jwt'); 

// Protect the /api routes with JWT 
app.use('/api', expressJwt({secret: secret})); 

app.use(express.json()); 
app.use(express.urlencoded()); 

如果你想验证你可以在你Express服务器创建此功能:

app.post('/authenticate', function (req, res) { 
    //if is invalid, return 401 
    if (!(req.body.username === 'john.doe' && req.body.password === 'foobar')) { 
    res.send(401, 'Wrong user or password'); 
    return; 
    } 

    var profile = { 
    first_name: 'John', 
    last_name: 'Doe', 
    email: '[email protected]', 
    id: 123 
    }; 

    // We are sending the profile inside the token 
    var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 }); 

    res.json({ token: token }); 
}); 

而对于保护呼叫东西以/ api开头:

app.get('/api/restricted', function (req, res) { 
    console.log('user ' + req.user.email + ' is calling /api/restricted'); 
    res.json({ 
    name: 'foo' 
    }); 
}); 

在您的Angular应用程序中n您可以登录:

$http 
     .post('/authenticate', $scope.user) 
     .success(function (data, status, headers, config) { 
     $window.sessionStorage.token = data.token; 
     $scope.message = 'Welcome'; 
     }) 
     .error(function (data, status, headers, config) { 
     // Erase the token if the user fails to log in 
     delete $window.sessionStorage.token; 

     // Handle login errors here 
     $scope.message = 'Error: Invalid user or password'; 
     }); 

,并通过创建一个认证拦截器,它会自动与每个请求发送令牌:

myApp.factory('authInterceptor', function ($rootScope, $q, $window) { 
    return { 
    request: function (config) { 
     config.headers = config.headers || {}; 
     if ($window.sessionStorage.token) { 
     config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token; 
     } 
     return config; 
    }, 
    response: function (response) { 
     if (response.status === 401) { 
     // handle the case where the user is not authenticated 
     } 
     return response || $q.when(response); 
    } 
    }; 
}); 

myApp.config(function ($httpProvider) { 
    $httpProvider.interceptors.push('authInterceptor'); 
}); 

如果您有支持不支持本地存储旧的浏览器。您可以将$window.sessionStorage换成类似AmplifyJS的库(http://amplifyjs.com/)。放大例如使用任何本地存储可用。这将转化中是这样的:

if (data.status === 'OK') { 
     //Save the data using Amplify.js 
     localStorage.save('sessionToken', data.token); 
     //This doesn't work on the file protocol or on some older browsers 
     //$window.sessionStorage.token = data.token; 
     $location.path('/pep'); 
    } 
    }).error(function (error) { 
    // Erase the token if the user fails to log in 
    localStorage.save('sessionToken', null); 
    // Handle login errors here 
    $scope.message = 'Error: Invalid user or password'; 
    }); 

,我们交换了authintercepter:

angular.module('myApp.authInterceptor', ['myApp.localStorage']).factory('authInterceptor', [ 
    '$rootScope', 
    '$q', 
    'localStorage', 
    function ($rootScope, $q, localStorage) { 
    return { 
     request: function (config) { 
     config.headers = config.headers || {}; 
     config.headers.Authorization = 'Bearer ' + localStorage.retrieve('sessionToken'); 
     return config; 
     }, 
     response: function (response) { 
     if (response.status === 401) { 
     } 
     return response || $q.when(response); 
     } 
    }; 
    } 
]); 

你可以找到一切,除了本文中AmplifyJS:

http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

+0

好的,但是,我每次都*发送用户名和密码,并且可能会发生XSS攻击。为什么不使用cookie呢?至少密码不是很简单 – nick

+3

不需要。您只需通过HTTP POST发送一次您的用户名和密码。这与使用Cookie的登录无异。之后,您使用HTTP标头中的标记来验证自己,而不是使用HTTP标头中设置的cookie进行验证。如果你想保护自己免受XSS攻击,你必须过滤应用程序收到的所有输入。使用HTTPS传输数据是有意义的。基于令牌的身份验证的优点在我的答案中链接的帖子中描述。 –

有一个看看yeoman发电机的角度和节点?发电机 - 角度 - 满堆具有用于使用护照进行用户认证的非常好的结构。

你可以在这里看到一个例子:

代码:https://github.com/DaftMonk/fullstack-demo

结果:http://fullstack-demo.herokuapp.com/

希望它能帮助!

我使用generator-angular-fullstack,/ api服务不安全,从/ api/users/me获取您的_id,注销并转到/ api/users/your_id_here,您会发现/ api不安全。