如何使用服务器管理移动应用程序中的 OAuth 流程

How to manage OAuth flow in mobile application with server

我正在开发一款带有 flutter(移动客户端)的运动移动应用程序,用于跟踪其用户 activity 数据。在跟踪 activity(游泳、运行、步行...)后,它调用我开发的 REST API(使用 springboot)传递 activity 数据和 POST。然后,我的用户将能够查看他使用 GET 调用 REST API 的跟踪活动的日志。

据我所知,我自己的跟踪开发不如Strava,Garmin,Huawei等,我想让我的应用程序用户连接他们的Strava,Garmin等帐户以获得他们的活动数据,因此我需要用户授权我的应用使用 OAuth 获取该数据。

在第一种方法中,我已经成功地使用授权码授予开发了所有的 OAuth 流程。授权服务器登录由 flutter 在用户代理(chrome 选项卡)中启动,一旦资源所有者完成登录并授权我的 flutter 应用程序,我的 flutter 应用程序将获取授权代码和对授权服务器的调用得到令牌。所以我可以说,我的客户就是我的 flutter App。当 oauth 流程完成后,我将令牌发送到我的 Rest API 以便将它们存储在数据库中。

我的第一个想法是将这些令牌发送到我的后端应用程序,以便将它们存储在数据库中并开发一个进程来获取这些令牌、咨询资源服务器、解析每个资源服务器 json 响应活动以我剩下的 API activity 建模并存储在我的数据库中。然后,如果资源所有者调用我的 Rest API 咨询其活动,他将收到所有活动的响应(移动应用程序跟踪的活动 + Strava、Garmin、资源服务器等存储在我的数据库中)。

当用户按下同步按钮并直接在我的客户端映射这些响应时,我已经放弃了直接从我的客户端调用资源服务器和我的休息 api 的选项,因为我需要这些资源服务器在后端响应的数据,以实现勋章功能。此外,Strava、Garmin 等都有使用限制,我不想让我的资源所有者能够按他们想要的时间按下按钮。

这是我第一个想法的流程:

步骤:

  1. 客户端调用授权服务器启动用户代理以进行 oauth 登录。为了让资源所有者登录并授权。 url 和参数是硬编码的,在我的客户端中是硬编码的。

  2. 资源所有者登录并授权客户端。

  3. 回调与代码一起发送。

  4. 客户端捕获回调代码并向授权服务器发送 post 以获取令牌。由于一些授权服务器接受 PKCE,我尽可能使用 PKCE,以避免攻击和在我的客户端中硬编码我的客户端密码。其他像 Strava 的不允许 PKCE,所以我必须在我的客户端中硬编码客户端密码才能获得令牌。

  5. 一旦令牌 return 发送到我的客户,我将它们发送给我的休息 api 并存储在识别令牌资源所有者的数据库中。

调用资源服务器:

  1. 一个周期性进程获取每个资源所有者的令牌,并使用从每个资源服务器 return 编辑的活动更新我的数据库。

  2. 资源所有者调用其余api并获取所有活动。

第一个想法的问题在于,一些授权服务器允许实施 PKCE (Fitbit),而其他服务器使用客户端密码来创建令牌 (Strava)。因为我需要客户端密码来获取其中一些授权服务器的令牌,所以我在客户端中对密码进行了硬编码,这并不安全。

我知道将客户端机密插入客户端很危险,因为黑客可以反编译我的客户端并获取客户端机密。如果授权服务器中不允许 PKCE,我不知道如何在不对客户端密码进行硬编码的情况下获取 Strava 的资源所有者令牌。

由于我不想在我的客户端中硬编码我的客户端机密,因为它不安全并且我想将令牌存储在我的数据库中,所以我认为我的第一种方法不是一个好的选择。此外,我正在为我的 REST API 创建一个 POST 请求,以便将访问令牌和刷新令牌存储在我的数据库中,如果我没记错的话,该过程可以直接从后端完成.

我的情况是我开发了一个 public 客户端(移动应用程序),它对客户端机密进行了硬编码,因为我不知道如何在 PKCE 不允许时避免这样做授权服务器获取令牌。

所以在考虑了所有这些问题之后,我的第二个想法是利用我的 REST API 并从那里调用授权服务器。所以我的客户会保密,我会使用服务器端应用程序执行 OAuth 流程。

我的想法是基于这张图片。

为了避免在我的移动客户端中对客户端密码进行硬编码,以下基于图像的代码流能否正常工作并安全地连接到 Strava、Garmin、Polar...?

Strava 连接示例:

移动客户端

用户代理

移动客户端

REST API 客户端

过程

第二种方法是否是处理客户端机密以避免使它们成为 public 的好方法?或者我做错了什么?我可以按照什么流程以正确的方式进行操作?我真的被这个案例困住了,因为我是 OAuth 世界的新手,我对阅读的所有信息感到不知所措。

据我了解,这里主要关注的是,您想避免对客户端密码进行硬编码。
我以 keycloak 作为授权服务器的示例,但这在其他授权服务器中也是相同的,因为实现必须遵循标准
在授权服务器中有两种类型的客户端,一种是
1.Confidential 客户端 - 这些客户端需要 client-id 和 client-secret 在您的 Rest api 调用中传递

CURL 是这样的,需要客户端密码

curl --location --request POST 'http://localhost:8080/auth/realms/testrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=confidentialclient' \
--data-urlencode 'client_secret=<CLIENT_SECRET_VALUE>' \
--data-urlencode 'code=<AUTHORIZATION_CODE_VALUE>' \
--data-urlencode 'redirect_uri=http://localhost:8080/callback' \
--data-urlencode 'code_verifier=<CODE_VERIFIER_PKCE>'

2.Public 客户端 - 如果您使用此选项创建客户端,则无需传递客户端密码。在进行 API 调用时,仅 client-id 就足够了。

对于 public 客户,您无需传递客户密码,少了一个需要担心的问题

curl --location --request POST 'http://localhost:8080/auth/realms/testrealm/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=publiclcient' \
--data-urlencode 'code=<AUTHORIZATION_CODE_VALUE>' \
--data-urlencode 'redirect_uri=http://localhost:8080/callback' \
--data-urlencode 'code_verifier=<CODE_VERIFIER_PKCE>'

大多数标准授权服务器都应该提供这些选项。

您介绍的第二种方法是实现 OAuth 流程的有效方法。您的后端可以充当您需要的机密客户端,您可以安全地从授权服务器获取令牌。这种模式有时被称为 Backand-For-Frontend(尽管现在这个名字有点过载)。在 Curity,我们描述了类似的东西,我们称之为令牌处理程序模式。前提是相似的,但我们的主要目标是可以利用 cookie 来管理会话的单页应用程序。

当然,您接下来需要的是在您的后端和移动应用程序之间创建一个安全会话。在浏览器应用程序中,您只需使用带有 cookie 的普通旧会话。您必须确保在调用您的后端时没有人可以冒充您的应用程序的用户。

您可以探索的另一件事是 Stravia 是否支持动态客户端注册标准。使用 DCR,您可以将移动应用程序的每个实例注册为单独的客户端。然后,这些客户端中的每一个都会收到自己的客户端 ID 和密码。再也不用担心秘密被盗

您也可以联系 Stravia,询问他们为什么不支持 public 客户使用 PKCE,以及​​他们是否打算这样做。 PKCE 是一个重要的安全 OAuth 标准,公司应该寻求支持它。