我正在nodejs服务器中编写一个前端在emberjs和后端/服务器端的应用程序。我配置了emberjs,以便用户可以使用第三方Oauth(谷歌,推特,Facebook)登录/注册。我有一个后端用快速nodejs服务器编写,它承载RESTful API。
我没有DB连接到emberjs,我认为我不应该,因为它是严格的客户端代码。我打算使用JWT在客户端和服务器端之间进行通信。当用户使用他们的oauth cred登录时,我会使用uid,name,login,access_token和其他详细信息从提供程序返回一个JSON对象。
我正在努力选择如何处理用户注册的策略。没有注册过程,因为它是OAuth。因此流程是如果用户不在我的数据库中,则创建它。我不支持电子邮件/密码验证。当用户第一次使用OAuth提供商登录时,流量会是多少? emberjs是否应该在每次登录时将所有详细信息发送到后端,以便后端可以向db添加新用户?
我的JWT机构应该包含哪些内容?我在想uid和提供者提供的访问令牌。我能想到的一个问题是提供者特定的访问令牌可以改变。用户可以从提供商的站点撤消令牌,并使用emberjs再次注册。
我愿意在任何其他javascript客户端框架中编写前端,如果它更容易。
答案 0 :(得分:26)
如果我们讨论的不仅是工作而且还有安全的无状态身份验证,您需要考虑使用access
和refresh
令牌的正确策略。
访问令牌是一种提供对受保护资源的访问权限的令牌。
Expiration
此处可能会在约1小时内安装(取决于您的考虑因素)。
刷新令牌是一种特殊令牌,如果已过期或用户会话已更新,则应使用该令牌生成其他access token
。显然你需要使它长寿(与access token
相比)并尽可能安全。
Expiration
此处可能会在大约10天甚至更长时间内安装(也取决于您的考虑因素)。
仅供参考:由于refresh tokens
存在很长时间,为了使它们真正安全,您可能希望将它们存储在数据库中(很少会执行刷新令牌请求)。通过这种方式,让我们说,即使您的刷新令牌以某种方式被黑客攻击并且有人重新生成access/refresh
令牌,当然您将失去权限,但是您仍然可以登录系统,因为您知道登录/通过(在如果您将在以后使用它们,或者只是通过任何社交网络登录。
存储这些令牌的位置?
基本上有两个常见的地方:
很高兴,但同时又有风险。可以通过同一域上的JavaScript代码访问存储。这意味着,如果您有XSS,您的令牌可能会遭到入侵。因此,通过选择此方法,您必须注意并编码/转义所有不受信任的数据。即使你这样做了,我也很确定你使用了一些第三方客户端模块,并且不能保证它们中的任何一个都有恶意代码。
此外,Web Storage
在转移过程中不会强制执行任何安全标准。因此,您需要确保通过HTTPS
发送JWT,而不是HTTP
。
使用特定的HttpOnly
选项,无法通过javascript访问Cookie,并且不受XSS的影响。您还可以设置Secure
cookie标志,以保证cookie仅通过HTTPS发送。
但是,Cookie容易受到不同类型的攻击:跨站点请求伪造(CSRF)。
在这种情况下,可以通过使用某种同步令牌模式来阻止CSRF
。在Security Considerations部分的AngularJS
中有很好的实施。
您可能想要关注的article。
说明一般情况如何:
关于 JWT 本身的几句话:
要说清楚Auth0家伙真的很酷JWT Debugger。
共有2种(有时3种)常见声明类型:public
,private
(和reserved)。
JWT
身体的一个例子(有效载荷,可以是你想要的任何东西):
{
name: "Dave Doe",
isAdmin: true,
providerToken: '...' // should be verified then separately
}
有关JWT
结构的更多信息,您会发现here。
答案 1 :(得分:4)
回答您提出的两个具体问题:
用户使用OAuth提供程序登录时的流程是什么? 第一次? emberjs是否应该将所有细节发送到后端 每次登录以便后端可以将新用户添加到数据库?
每当用户注册或通过oauth登录并且您的客户端收到新的访问令牌时,我将 upsert (更新或插入)到用户表(或集合)中您从oauth提供程序API检索到的有关用户的任何新信息或更新信息。我建议将其直接存储在每个用户记录中,以确保访问令牌和关联的配置文件信息以原子方式更改。一般来说,我通常将其组合成某种中间件,当存在新令牌时,它会自动执行这些步骤。
我的JWT机构应该包含哪些内容?我在想uid和提供者 提供的访问令牌。我能想到的一个问题是提供者 特定访问令牌可以更改。用户可以撤消令牌 提供者的网站,并再次与emberjs签约。
JWT机构通常由用户声明组成。我个人认为将提供者访问令牌存储在JWT令牌体中几乎没有什么好处,因为它对您的客户端应用程序几乎没有任何好处(除非您从客户端对其API执行大量直接API调用,我更喜欢做那些调用服务器端并向我的应用程序客户端发回一个遵循我自己的界面的标准化声明集。通过编写自己的声明界面,您无需解决来自客户端应用程序中多个提供程序的各种差异。这方面的一个示例是将Twitter和Facebook特定字段合并,这些字段在API中以不同的名称命名为存储在用户配置文件表中的公共字段,然后将本地配置文件字段作为JWT正文中的声明嵌入,以供客户端应用解释。这样做还有一个额外的好处,就是你不会在未加密的JWT令牌中保留任何可能泄露的数据。
无论您是否在JWT令牌主体中存储oauth提供程序提供的访问令牌,每次配置文件数据发生更改时都需要授予新的JWT令牌(您可以设置绕过发布新JWT令牌的机制,如果没有发生配置文件更新,前一个令牌仍然很好。
除了您在JWT令牌主体中存储为声明的任何配置文件字段之外,我将始终定义标准JWT token body fields:
{
iss: "https://YOUR_NAMESPACE",
sub: "{connection}|{user_id}",
aud: "YOUR_CLIENT_ID",
exp: 1372674336,
iat: 1372638336
}
答案 2 :(得分:3)
对于任何OAuth工作流程,您都应该使用passportjs库。您还应该阅读完整的文档。这很容易理解,但我犯了一个错误,即第一次不读完整件事并且挣扎。它包含超过300个提供商和颁发令牌的OAuth身份验证。
尽管如此,如果你想手动完成或想要一个基本的理解,这里是我使用的流程:
前端有一个登录页面,列出了使用Google / Facebook等登录OAuth的方式。
成功的OAuth会产生uid,login,access_token等(JSON对象)
您将JSON对象发布到Node.js应用程序中的myDA = New OleDbDataAdapter
myDataSet = New DataSet
myDA.SelectCommand = New OleDbCommand()
Try
cmd = New OleDbCommand()
cmd.CommandText = "Select sum(Paid) as total from tblorder"
cmd.Connection = con
If con.State = ConnectionState.Closed Then con.Open()
myDA.SelectCommand = cmd
myDA.Fill(myDataSet, "TTotal")
TextBox1.Text = CStr(myDataSet.Tables("TTotal").Rows(0).Item(0))
DataGridView1.DataSource = myDataSet.Tables("TTotal").DefaultView
con.Close()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
路由。 (是的,无论是新用户还是现有用户,都会发送整个响应。此处发送额外数据比做两个请求更好)
后端应用程序会读取/login/
和uid
。通过以下(https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken)确保access_token
有效,或使用访问令牌向提供商请求用户数据。 (由于OAuth访问令牌是基于每个应用程序/开发人员生成的,因此无效访问令牌将失败)现在,搜索后端数据库。
如果数据库中存在access_token
,则在数据库中更新用户的access_token和expiresIn。 (access_token允许您从Facebook获取该特定用户的更多信息,并且通常可以提供几个小时的访问权限。)
否则,您使用uid,登录等信息创建新用户。
更新access_token或创建新用户后,发送包含uid
的JWT令牌。 (用秘密对jwt进行编码,这将确保它由您发送并且未被篡改。结帐https://github.com/auth0/express-jwt)
在用户从uid
收到jwt后的前端,请/login
sessionStorage
在前端,还要添加以下内容:
sessionStorage.setItem('jwt', token);
这将确保如果存在jwt令牌,则会随每个请求一起发送。
if ($window.sessionStorage.token) {
xhr.setRequestHeader("Authorization", $window.sessionStorage.token);
}
这将验证路径中任何内容的jwt,确保用户已登录,否则不允许访问并重定向到登录页面。这里的例外情况是app.use(jwt({ secret: 'shhhhhhared-secret'}).unless({path: ['/login']}));
,因为您可以在这里为新的或未经身份验证的用户提供JWT。
您可以在Github网址上找到有关如何获取令牌以及找出您当前正在服务的用户请求的更多信息。