OAuth 新手指南:使用 Google API 理解访问令牌和授权码 什么是授权码和访问令牌?OAuth 分步指南 使用刷新令牌获取新的访问令牌

2025-05-27

OAuth 初学者指南:使用 Google API 了解访问令牌和授权码

什么是授权码和访问令牌?

OAuth 一步步操作

使用刷新令牌获取新的访问令牌

作为用户,使用您的 Google 帐户(或 Facebook、Twitter 等)登录其他服务非常简单方便。

  1. 点击“使用 Google 登录”按钮
  2. 您将被重定向到同意屏幕
  3. 您点击“允许”
  4. 页面重定向到实际应用程序

但实际上,底层有很多事情要做,了解这些对开发者来说很有帮助。我第一次在 Rails 中实现 OAuth 时,使用了一个外部库(sorceryGem 的外部模块),这让我很容易忽略整个流程。然而,每当我想要定制某些东西时,我意识到有必要更好地理解它。于是我决定在不使用任何身份验证辅助 Gem 的情况下重新实现这个流程。

根据我所学的知识,我撰写了这篇关于 OAuth 流程以及授权码和访问/刷新令牌的基本解释。我使用 Google 作为 OAuth 提供商。

这篇文章面向那些对 OAuth 不太熟悉的人。换句话说,如果你已经了解它的工作原理,并且能够理解 Google 的 OAuth指南,那么这篇文章对你来说可能太过基础。

什么是授权码和访问令牌?

在 OAuth 流程中,您的应用需要向 Google 发送两个请求。第一个请求用于获取授权码,第二个请求用于获取访问令牌。它们都采用长字符串的形式,但用途不同。
这种类似的术语乍一看可能有点难以理解,所以我们先简单介绍一下它们的含义。

我猜这个界面看起来很眼熟。授权码是用户在此界面同意后,Google 会将其发送回你的应用。

此代码可用于获取访问令牌。收到授权码后,您可以将其放入参数中,然后向 Google 发送第二个请求,本质上是说“请给我一个访问令牌,以便我可以代表该用户发送请求?”
Google 对此的响应应该包含访问令牌。通过将此令牌放入请求标头中,您可以执行诸如在用户的 Google 日历中创建新活动或访问用户的 Gmail 等操作。

需要注意的关键事项:

授权码

  • 仅限一次性使用,因为其唯一用途是将其交换为访问令牌
  • 过期非常快(根据这篇文章,OAuth 协议建议的最大时间为 10 分钟,许多服务的授权码过期时间甚至更早)

访问令牌

  • 可以使用授权码获取
  • 代表用户将任何 API 请求的标头放入 Google
  • 一小时后过期(如果您使用 Google 以外的其他服务,过期时间可能会有所不同)

访问令牌过期一小时后该怎么办?是否需要让用户重新登录才能获取新的访问令牌?
不需要。您可以获取一个叫做刷新令牌 的东西,它允许您获取新的访问令牌。更多详情请参阅本文的最后一部分。

OAuth 一步步操作

让我们再回顾一下 OAuth 的基本流程,不过这次我们将从开发者的角度,而不是用户的角度来探讨。我还附上了一些 Ruby 代码片段。

注意:我跳过了您必须执行的第一个配置,即在Google API 控制台中创建client_id。本指南将引导您完成这些步骤。client_secret

步骤 1. 用户点击您应用中的“使用 Google 帐户登录”按钮

第 2 步:重定向到 Google 同意屏幕

就我而言,单击按钮将调用oauths_controlleroauth方法,然后该方法将重定向页面。

# oauths_controller.rb
def oauth
  args = {
    client_id: ENV['GOOGLE_CLIENT_ID'],
    response_type: 'code',
    scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar',
    redirect_uri: 'http://my-app.com/oauth/callback?provider=google',
    access_type: 'offline'
  }
  redirect_to 'https://accounts.google.com/o/oauth2/v2/auth?' + args.to_query
end

Enter fullscreen mode Exit fullscreen mode

查询参数的简单解释:

  • client_id是您在 Google API 控制台中创建的。我刚刚将它存储在环境变量中。
  • response_type: 'code'表示您需要授权码来获取访问令牌。
  • scope定义您需要哪些类型的权限。除了用户的姓名和电子邮件地址之外,我还需要访问用户的 Google 日历,因此我设置了上述三个范围。完整的范围列表可在文档中找到。
  • redirect_uri是用户点击“允许”后 Google 重定向到的 URI。您不能在此处输入任何随机 URI;它需要与您在 Google API 控制台中添加的 URI 之一匹配。
  • access_type: 'offline'与我上面提到的刷新令牌有关,我们稍后会介绍这一点。

步骤 3. 用户在同意屏幕上点击“允许”

步骤 4. 页面重定向至您的callback_uri

Google 处理了这部分。在我的例子中,oauths_controllercallback方法被调用。

# routes.rb
get 'oauth/callback', to: 'oauths#callback'
Enter fullscreen mode Exit fullscreen mode

来自 Google 的此传入请求的参数包括授权码(在 中params[:code])。

步骤 5. 将授权码交换为访问令牌

接下来,您需要向 Google 的令牌端点(/oauth2/v4/token)发出 HTTP POST 请求,以获取访问令牌来换取您刚刚收到的授权码。

注意:我正在使用HTTPartygem 发出 HTTP 请求,但这当然不是强制性的。

# oauths_controller.rb
def callback
  # Exchange the authorization code for an access token (step 5)
  query = {
    code: params[:code],
    client_id: ENV['GOOGLE_CLIENT_ID'],
    client_secret: ENV['GOOGLE_CLIENT_SECRET'],
    redirect_uri: 'http://my-app.com/oauth/callback?provider=google',
    grant_type: 'authorization_code'
  }
  response = HTTParty.post('https://www.googleapis.com/oauth2/v4/token', query: query)

  # Save the access token (step 6)
  session[:access_token] = response['access_token']
end
Enter fullscreen mode Exit fullscreen mode

至于此 POST 请求中的参数,如果您感兴趣的话,本文提供了很好的解释。

步骤 6.保存访问令牌

如上面代码片段的最后一部分所示,我将返回值保存access_token在了会话中。现在,每当我想代表用户向 Google API 发出请求时,都可以使用此令牌进行操作。

例如,要获取用户的 Google 日历事件列表:

headers = {
  'Content-Type': 'application/json',
  'Authorization': "Bearer #{session[:access_token]}"
}
HTTParty.get(
  'https://www.googleapis.com/calendar/v3/calendars/primary/events',
  headers: headers
)

Enter fullscreen mode Exit fullscreen mode

好了!现在我们已经成功使用授权令牌实现了 OAuth 流程。

使用刷新令牌获取新的访问令牌

如上所述,访问令牌会在一定时间(例如 1 小时)后过期。如果您的应用登录信息也在同一时间或更早过期,您无需担心——用户无论如何都必须重新登录。

但是,如果您的应用允许用户登录更长时间怎么办(毕竟,在很多情况下,一小时后被踢出应用可能会很令人讨厌)?Google 的访问令牌仍然会过期,因此任何对 Google API 的请求都将被拒绝。

这就是刷新令牌的access_type: 'offline'作用所在。只要您在初始重定向(步骤 2)中指定,您就可以在与返回访问令牌(步骤 5)相同的响应中获得一个刷新令牌。

与访问令牌不同,刷新令牌没有设置有效期。如果您的访问令牌已过期,您可以使用刷新令牌通过 HTTP POST 请求获取新的访问令牌,如下所示:

query = {
  'client_id': ENV['GOOGLE_CLIENT_ID'],
  'client_secret': ENV['GOOGLE_CLIENT_SECRET'],
  # Assuming we've saved the refresh_token in the DB along with the user info
  'refresh_token': current_user.refresh_token,
  'grant_type': 'refresh_token',
}
response = HTTParty.post(
  'https://www.googleapis.com/oauth2/v4/token',
  query: query
)

session[:access_token] = response['access_token']
Enter fullscreen mode Exit fullscreen mode

刷新令牌陷阱

1. 你应该重复使用它们

通常,每次请求新的访问令牌时,您都应该使用相同的刷新令牌。因此,Google 仅在用户首次同意并登录您的应用时向您提供刷新令牌(请参阅离线访问文档):

此值指示 Google 授权服务器在您的应用程序第一次交换授权码以获取令牌时返回刷新令牌和访问令牌。

这意味着将刷新令牌存储在长期存储(如数据库)中非常重要。

注意:如果您确实需要在用户每次登录时获取新的刷新令牌,则可以在步骤 2prompt=consent中的授权请求中添加参数。这将要求用户每次登录时都同意,但响应始终会包含新的刷新令牌。

2. 它们也可能失效

虽然刷新令牌在一定时间后不会过期,但在某些情况下它们可能会变得无效(文档):

您必须编写代码来预测授予的刷新令牌可能不再起作用的情况。刷新令牌可能由于以下原因之一而停止工作:

  • 用户已撤销您应用的访问权限。
  • 刷新令牌已六个月未使用。
  • 用户更改了密码,并且刷新令牌包含 Gmail 范围。
  • 用户帐户已超出授予的(实时)刷新令牌数量上限。目前,每个客户端每个用户帐户最多可拥有 50 个刷新令牌。如果达到上限,创建新的刷新令牌将自动使最旧的刷新令牌失效,且不会发出任何警告。

例如,如果用户撤销了您应用的访问权限,任何使用现有刷新令牌获取新访问令牌的请求都将失效。在这种情况下,您需要让用户注销并重新登录才能获取新的刷新令牌。


在这篇文章中,我手动处理了所有事情,没有依赖devisesorcery或 之类的 gem omniauth-google-oauth2。我并不是说这是最好的方法;实际上,使用这些 gem 可能更容易。这篇文章的目的是讲解实际发生的情况,而不是盲目地让 gem 处理所有事情。感谢阅读!

文章来源:https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988
PREV
为什么我向每一位自学成才的开发人员推荐哈佛的 CS50x 在线课程
NEXT
编写优美的代码