我做这整个 API、客户端应用程序、Oauth/OpenId 连接的事情对吗?
Am I doing this whole API, client app, Oauth/OpenId Connect thing right?
我有一些编程经验,但仅限于 PHP 和 Java 企业系统。但现在我对我的新工作中的网络应用程序有了一些想法。由于我是新手,我想分享一下我是如何在服务器、浏览器应用程序和身份验证中使用 Google 的 OpenID Connect 完成整个 API(我阅读了很多关于 Oauth 和 OpenID 的内容连接,最有用的来源是:https://developers.google.com/identity/protocols/OpenIDConnect).
服务器:Laravel - hxxps://coolapp-api.mycompany.com
客户端:Angular - hxxps://coolapp.mycompany.com
TL;DR 版本:
1) 用户访问 hxxps://coolapp.mycompany.com,获取 Angular 应用程序登录页面。输入他们的电子邮件,单击“使用 Google 登录”;
2) 应用发送邮件至hxxps://coolapp-api.mycompany.com/api/sign-in。服务器使用所有需要的参数将用户重定向到 hxxps://accounts.google.com/o/oauth2/auth;
3) 用户登录到他们的 Google 帐户,如果这是他们第一次登录我的应用程序权限,然后 Google 将他们重定向到我的服务器 hxxps://coolapp-api.mycompany.com/sign-in/google/callback.服务器检查所有内容,如果一切正确,它会创建一个 JWT 令牌并将重定向发送到位于 hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN[=13= 的客户端应用程序]
4) 客户端应用程序获取令牌,将其存储在本地存储中,并在每次 API 调用时将其发送到服务器
更详细的版本:
1) 用户访问 hxxps://coolapp.mycompany.com,获取 Angular 应用程序登录页面。输入他们的电子邮件,单击“使用 Google 登录”;
2) 应用发送邮件至hxxps://coolapp-api.mycompany.com/api/sign-in。服务器创建一个状态令牌并将其存储在缓存中,与收到的电子邮件相关联。然后服务端创建Google的oauthURL并在响应体中发送给客户端。我尝试使用 HTTP 重定向来实现,但 Google 的服务器响应时出现 CORS 错误。 Angular 应用从响应中读取 Google 的 url 并转到那里。
3) 用户登录到他们的 Google 帐户,如果这是他们第一次登录我的应用程序权限,然后 Google 将他们重定向到我的服务器 hxxps://coolapp-api.mycompany.com/sign-in/google/callback?code=AUTHCODE&otherstuff。服务器将它收到的代码(以及所有其他需要的参数)发送到 hxxps://accounts.google.com/o/oauth2/token。它会收到包含该用户的电子邮件和基本信息的 id_token。这个应用程序不是 public,所以我不希望任何拥有 Google 帐户的人登录,只希望我将其电子邮件添加到服务器数据库的客户登录。所以现在服务器检查令牌中用户的电子邮件是否在数据库中。如果不是,它会向用户发送 HTTP 401 - 未经授权。然后服务器检查其缓存中与收到的电子邮件相关联的状态令牌。如果它等于通过 Google 的重定向收到的那个,那么服务器会创建另一个 JWT 令牌,但现在由我的服务器签名。最后,它使用新令牌向 hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN 发送 HTTP 重定向。
4) 客户端应用程序获取令牌,将其存储在本地存储中,并在每次 API 调用时将其发送到服务器
一些评论:
一切都是 HTTPS;
我已将最严格的 CSP 策略添加到我的 Laravel 服务器和 Angular 客户端;
目前应用仅支持Google的登录,正在开发中。稍后再补充。
我让我的服务器只在用户使用 google 登录后检查他们的电子邮件是否在数据库中,因为我喜欢这样的想法,即非授权用户应该没有信息关于任何事情。如果我在它之前进行了检查,那么在第一次往返期间,任何人都可以输入一封电子邮件并发现该电子邮件是否在我的系统中有一个帐户;
在最后一步,当我的服务器将 JWT 令牌发送到我的客户端应用程序时,我尝试在 cookie 中发送令牌,但由于我的 API 和我的客户端应用程序有不同域,我的客户端应用程序无法读取令牌。在 url 中发送它是我能找到的唯一解决方案。我尝试登录一个使用 Oauth 的流行应用程序,他们也是这样做的。
所以我的问题是:
我是不是做错了什么、不安全、奇怪?
非常感谢大家
1) 每次用户要登录时都输入电子邮件地址很繁琐。如果用户已经在 Google 登录,则不需要。用户只需单击 "Log in with Google" 按钮即可登录,无需输入任何内容。 state 参数可以是随机字符串 - 与用户的电子邮件无关。
2) 如果您希望您的后端处理来自 Google 的重定向(使用授权代码流 - 后端在 OAuth2 术语中具有客户端角色),后端还应该启动重定向到 Google - 不是通过发送包含重定向的数据 URL。要实现它,点击 "Log in with Google" 按钮后,执行整个页面导航(而不是 XHR 请求)到 /api/sign-in
如果后端 returns HTTP 302,浏览器将正确重定向到Google.
3) 在获取令牌并检查用户是否存在之前,您应该执行请求验证(state
参数)。
出现错误(访问被拒绝)时,您可以考虑将用户重定向到包含错误详细信息的错误页面,而不是返回 HTTP 401,因为 HTTP 代码会导致向用户显示一般错误屏幕。如果你想继续使用HTTP代码,我觉得HTTP 403 Forbidden会更合适。
4) 考虑使用 sessionStorage 而不是 localStorage。 sessionStorage 在关闭 browser/tab 后被清除,并且不在选项卡之间共享。它使它更安全,并允许用户在不同的浏览器选项卡中使用不同的身份。
你后台发的token,有效期有限制吗?用户是否需要在一段时间(短)后获得新令牌?否则,有效的令牌值可能会保留在 localStorage 和浏览器的页面历史记录中,这可能是一个安全问题。
您可以考虑使用您自己的 OAuth2 身份验证服务器(例如 RedHat Keycloak),它会接受 Google(以及后来的一些其他提供商)进行身份验证,并且它还会颁发您所接受的访问令牌后端。
我有一些编程经验,但仅限于 PHP 和 Java 企业系统。但现在我对我的新工作中的网络应用程序有了一些想法。由于我是新手,我想分享一下我是如何在服务器、浏览器应用程序和身份验证中使用 Google 的 OpenID Connect 完成整个 API(我阅读了很多关于 Oauth 和 OpenID 的内容连接,最有用的来源是:https://developers.google.com/identity/protocols/OpenIDConnect).
服务器:Laravel - hxxps://coolapp-api.mycompany.com
客户端:Angular - hxxps://coolapp.mycompany.com
TL;DR 版本:
1) 用户访问 hxxps://coolapp.mycompany.com,获取 Angular 应用程序登录页面。输入他们的电子邮件,单击“使用 Google 登录”;
2) 应用发送邮件至hxxps://coolapp-api.mycompany.com/api/sign-in。服务器使用所有需要的参数将用户重定向到 hxxps://accounts.google.com/o/oauth2/auth;
3) 用户登录到他们的 Google 帐户,如果这是他们第一次登录我的应用程序权限,然后 Google 将他们重定向到我的服务器 hxxps://coolapp-api.mycompany.com/sign-in/google/callback.服务器检查所有内容,如果一切正确,它会创建一个 JWT 令牌并将重定向发送到位于 hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN[=13= 的客户端应用程序]
4) 客户端应用程序获取令牌,将其存储在本地存储中,并在每次 API 调用时将其发送到服务器
更详细的版本:
1) 用户访问 hxxps://coolapp.mycompany.com,获取 Angular 应用程序登录页面。输入他们的电子邮件,单击“使用 Google 登录”;
2) 应用发送邮件至hxxps://coolapp-api.mycompany.com/api/sign-in。服务器创建一个状态令牌并将其存储在缓存中,与收到的电子邮件相关联。然后服务端创建Google的oauthURL并在响应体中发送给客户端。我尝试使用 HTTP 重定向来实现,但 Google 的服务器响应时出现 CORS 错误。 Angular 应用从响应中读取 Google 的 url 并转到那里。
3) 用户登录到他们的 Google 帐户,如果这是他们第一次登录我的应用程序权限,然后 Google 将他们重定向到我的服务器 hxxps://coolapp-api.mycompany.com/sign-in/google/callback?code=AUTHCODE&otherstuff。服务器将它收到的代码(以及所有其他需要的参数)发送到 hxxps://accounts.google.com/o/oauth2/token。它会收到包含该用户的电子邮件和基本信息的 id_token。这个应用程序不是 public,所以我不希望任何拥有 Google 帐户的人登录,只希望我将其电子邮件添加到服务器数据库的客户登录。所以现在服务器检查令牌中用户的电子邮件是否在数据库中。如果不是,它会向用户发送 HTTP 401 - 未经授权。然后服务器检查其缓存中与收到的电子邮件相关联的状态令牌。如果它等于通过 Google 的重定向收到的那个,那么服务器会创建另一个 JWT 令牌,但现在由我的服务器签名。最后,它使用新令牌向 hxxps://coolapp.mycompany.com/login/callback?token=JWT-TOKEN 发送 HTTP 重定向。
4) 客户端应用程序获取令牌,将其存储在本地存储中,并在每次 API 调用时将其发送到服务器
一些评论:
一切都是 HTTPS;
我已将最严格的 CSP 策略添加到我的 Laravel 服务器和 Angular 客户端;
目前应用仅支持Google的登录,正在开发中。稍后再补充。
我让我的服务器只在用户使用 google 登录后检查他们的电子邮件是否在数据库中,因为我喜欢这样的想法,即非授权用户应该没有信息关于任何事情。如果我在它之前进行了检查,那么在第一次往返期间,任何人都可以输入一封电子邮件并发现该电子邮件是否在我的系统中有一个帐户;
在最后一步,当我的服务器将 JWT 令牌发送到我的客户端应用程序时,我尝试在 cookie 中发送令牌,但由于我的 API 和我的客户端应用程序有不同域,我的客户端应用程序无法读取令牌。在 url 中发送它是我能找到的唯一解决方案。我尝试登录一个使用 Oauth 的流行应用程序,他们也是这样做的。
所以我的问题是:
我是不是做错了什么、不安全、奇怪?
非常感谢大家
1) 每次用户要登录时都输入电子邮件地址很繁琐。如果用户已经在 Google 登录,则不需要。用户只需单击 "Log in with Google" 按钮即可登录,无需输入任何内容。 state 参数可以是随机字符串 - 与用户的电子邮件无关。
2) 如果您希望您的后端处理来自 Google 的重定向(使用授权代码流 - 后端在 OAuth2 术语中具有客户端角色),后端还应该启动重定向到 Google - 不是通过发送包含重定向的数据 URL。要实现它,点击 "Log in with Google" 按钮后,执行整个页面导航(而不是 XHR 请求)到 /api/sign-in
如果后端 returns HTTP 302,浏览器将正确重定向到Google.
3) 在获取令牌并检查用户是否存在之前,您应该执行请求验证(state
参数)。
出现错误(访问被拒绝)时,您可以考虑将用户重定向到包含错误详细信息的错误页面,而不是返回 HTTP 401,因为 HTTP 代码会导致向用户显示一般错误屏幕。如果你想继续使用HTTP代码,我觉得HTTP 403 Forbidden会更合适。
4) 考虑使用 sessionStorage 而不是 localStorage。 sessionStorage 在关闭 browser/tab 后被清除,并且不在选项卡之间共享。它使它更安全,并允许用户在不同的浏览器选项卡中使用不同的身份。 你后台发的token,有效期有限制吗?用户是否需要在一段时间(短)后获得新令牌?否则,有效的令牌值可能会保留在 localStorage 和浏览器的页面历史记录中,这可能是一个安全问题。
您可以考虑使用您自己的 OAuth2 身份验证服务器(例如 RedHat Keycloak),它会接受 Google(以及后来的一些其他提供商)进行身份验证,并且它还会颁发您所接受的访问令牌后端。