护照&JWT&Google/Facebook策略 - 我如何将JWT和Google/Facebook策略结合起来?

23
这个问题是给那些熟悉以下内容的人的:
- Node.js - Express - Passport - 使用passport进行JWT身份验证(JSON Web Tokens) - Facebook OAuth2.0或Google OAuth2.0
我已经学习了一些在线课程,并且了解如何完成以下两件事情:
1. 使用Passport Local Strategy + JWT Tokens进行身份验证。 2. 使用Passport Google/Facebook Strategy + Cookie/Session进行身份验证。
我正在尝试将这两个课程的内容结合起来。我想使用Google Strategy + JWT Authentication。我想使用JWT而不是Cookie,因为我的应用程序将是一个Web/移动/平板应用程序,我需要从不同的域访问API。
我遇到了两个问题:
要启动Google/Facebook OAuth流程,您需要调用'/auth/facebook'或'/auth/google'。两种OAuth流程基本上都是相同的,所以现在我说'/auth/google',我指的是任何一种。现在我遇到的问题是:在客户端上,我是否应该使用href按钮链接还是axios/ajax调用'/auth/google'路由?如果我使用href或axios/ajax方法,我仍然会遇到两种解决方案的问题。
href方法的问题:
当我将< a >标签分配给'/auth/google'时,身份验证完全正常工作。用户通过Google Auth流程,登录并调用'/auth/google/callback'路由。现在我的问题是,我如何正确地将JWT令牌从'/auth/google/callback'发送回客户端?

经过大量的谷歌搜索,我发现人们只是将JWT从oauth回调中作为重定向查询参数传递回客户端。例如:

res.redirect(301, `/dashboard?token=${tokenForUser(req.user)}`);

我对此的问题是,现在认证能力保存在我的浏览器历史记录中!我可以注销(销毁存储在localStorage中的令牌),然后简单地查看我的浏览器url历史记录,返回包含查询参数中令牌的url,我就会自动重新登录而无需通过Google策略进行验证!这是一个巨大的安全漏洞,显然不是正确的方法。
axios/ajax方法的问题: 现在,在我解释这个问题之前,我确定如果我让它工作,它将解决我之前遇到的所有href问题。如果我成功从axios.get()调用中调用“/google/auth”,并在响应体中接收JWT,则不会将令牌作为url参数发送,并且不会保存在浏览器历史记录中!完美吧?但是,这种方法仍然存在一些问题:(
当尝试调用axios.get('/auth/google')时,我会收到以下错误:

enter image description here

我尝试解决这个问题的方法:

  • 我在我的npm服务器上安装了cors,并在index.js中添加了app.use(cors());
  • 我尝试将 "http://localhost:3000" 添加到 Google 开发者控制台中的“已授权 JavaScript 起源”中。

这些解决方案都没有解决问题,所以我真的感到很困惑。我想使用axios/ajax方法,但不确定如何解决这个cors错误。

非常抱歉信息这么长,但我真的觉得必须提供所有信息,才能让你正确地帮助我。

再次感谢,期待您的回复!

3个回答

26
我用以下方法解决了这个问题:
  1. 在前端(可以是移动应用程序)上,我向 Google(或 Facebook)发出登录请求,用户选择账户并登录后,我收到的响应包含 Google 授权令牌和基本用户信息。
  2. 然后,我将该 Google 授权令牌发送到后端,在那里我的 API 发送了另一个请求到 Google API,以确认该令牌。 (参见步骤 5)
  3. 成功请求后,您会收到基本的用户信息和电子邮件。此时,您可以假设用户通过 Google 登录是可行的,因为 Google 检查返回了“好”的结果。
  4. 然后,您只需使用该电子邮件注册或登录用户并创建该 JWT 令牌。
  5. 将令牌返回给客户端,以便将来使用。

希望对您有所帮助。我已经多次实施过这个方法,并且证明它是一个好的解决方案。


1
嗨,我有一个关于这个答案的问题。你能告诉我更多关于第二步的信息吗? 你被重定向回前端,然后从那里你要做什么? - Kevin.a
1
@Kevin.a 我拿到重定向后获得的令牌,将其发送到我的后端API请求中,后端API将使用Google API验证令牌并创建JWT令牌,在响应中发送。我将保存JWT到cookies并在未来的请求中使用它。 - vedran
你会在用户数据中保存Google账户ID以避免账户重复吗? - Chemi Adel
@ChemiAdel 我知道可以通过电子邮件判断用户是否存在,但你也可以使用Google账户ID。 - vedran

22

虽然已经有了好的答案,但我想用例子来补充更多信息。

  • Passport的谷歌/脸书策略是基于会话的,它将用户信息存储在cookie中,这是不可取的。因此,我们需要先禁用它。

为了禁用会话,我们需要修改重定向路由器。例如,如果我们有如下重定向路径/google/redirect,我们需要将{ session: false }对象作为参数传递。

router.get('/google/redirect', passport.authenticate('google', { session: false }), (req, res)=> {
    console.log(":::::::::: user in the redirect", req.user);
    //GENERATE JWT TOKEN USING USER
    res.send(TOKEN);
})

这个用户来自哪里?这个用户来自 Passport 的回调函数。在之前的代码片段中,我们添加了 passport.authenticate(....) 这个中间件启动了 Passport 的 Google 策略回调,处理用户信息。例如:

passport.use(
    new GoogleStrategy({
        callbackURL: '/google/redirect',
        clientID: YOUR_GOOGLE_CLIENT_ID
        clientSecret: YOUR_GOOGLE_SECRET_KEY
    }, 
    (accessToken, refreshToken, profile, done)=>{
        console.log('passport callback function fired');

        // FETCH USER FROM DB, IF DOESN'T EXIST CREATE ONE

        done(null, user);

    })
)

好的,我们已经成功地将JWT和Google/Facebook策略结合起来了。


谢谢您的回答。然而,我不确定如何处理最后一步。我正在将TOKEN发送回客户端(回调路由),并且我可以在那里看到它,但是我该如何将它保存在客户端的localStorage中。 - Murakami
最好的解释。谢谢Ramjan。 - ValRob
唯一的问题是在浏览器中,URL 将会像 /google/redirect?code=xxxxx 这样。你不希望将这个代码显示给最终用户。我认为,我们应该使用 successRedirect 而不是返回令牌 res.send(token),然后再返回令牌以避免向最终用户显示 ?code=xxxx - theprogrammer
有没有办法在客户端获取给定的 JWT?也许客户端可以在挂载钩子中检查 URL 是否包含 ?code=XXX,并将其添加到本地存储中? - Alexy

-1
我找到的解决方案是在弹出窗口(window.open)中执行OAuth流程,利用预定义的回调函数在成功验证后将令牌传递给前端。
以下是相关的代码示例,取自此教程: https://www.sitepoint.com/spa-social-login-google-facebook/ 这是预定义的回调函数和初始打开方法,从您的前端调用:
window.authenticateCallback = function(token) {
  accessToken = token;
};

window.open('/api/authentication/' + provider + '/start');

这是您的OAuth回调URL在成功验证后应返回的内容(这是弹出窗口中的最后一步/页面):
<!-- src/public/authenticated.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Authenticated</title>
  </head>
  <body>
    Authenticated successfully.

    <script type="text/javascript">
      window.opener.authenticateCallback('{{token}}');
      window.close();
    </script>
  </body>
</html>

你的令牌将会在预定义回调函数中变得可用,这样你就可以轻松地将其保存在localStorage中。

我想,你可以在同一个窗口中完成OAuth流程(无弹出),并返回一个类似上面的HTML页面,只需保存令牌并立即将用户重定向到仪表板。

但是,如果你的前端域名与api / auth服务器不同,则可能需要从api / auth服务器重定向到前端,使用一次性、时间敏感的令牌(由api / auth服务器生成),然后前端可以使用它来调用和接收(使用axios)实际的令牌。这样就不会有浏览器历史安全问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接