【问题标题】:Rails Google Client API - unable to exchange a refresh token for access tokenRails Google Client API - 无法用刷新令牌交换访问令牌
【发布时间】:2012-09-16 08:48:15
【问题描述】:

在我的机器上遇到了一些SSL issues 之后,我仍在尝试通过 Google Ruby Client API 访问用户的 Blogger 帐户。我正在使用以下内容:

  • Rails 3.2.3
  • Ruby 1.9.3
  • oauth2 (0.8.0)
  • omniauth (1.1.1)
  • omniauth-google-oauth2 (0.1.13)
  • google-api-client (0.4.6)

我可以在认证时通过 Google API 成功认证用户并访问他们的博客。当用户登录时,我存储从 Google 收到的 access_tokenrefresh_token。在access_token 到期之前一切正常。我正在尝试构建将refresh_token 交换为新access_token 的功能,但不断遇到墙壁。以client documentation 为例,这是我正在使用的代码:

  client = Google::APIClient.new
  token_pair = auth.oauth_token   # access_token and refresh_token received during authentication

  # Load the access token if it's available
  if token_pair  
    client.authorization.update_token!(token_pair.to_hash)
  end            

  # Update access token if expired
  if client.authorization.refresh_token && client.authorization.expired?
    client.authorization.fetch_access_token!
  end

  blogger = client.discovered_api('blogger', 'v3')
  result = client.execute(
      api_method: blogger.blogs.list_by_user,
      parameters: {'userId' => "self", 'fields' => 'items(description,id,name,url)'},
      headers: {'Content-Type' => 'application/json'})

access_token 有效时,此代码可以完美运行。不过,一旦到期,我就会看到 2 个问题:

  1. 即使我知道令牌已过期(我已经检查了数据库中的 expires_at 值),client.authorization.expired? 返回 false -- 除了使用数据库中的值?
  2. 当我强制执行 client.authorization.fetch_access_token! 时,我收到 invalid_request 错误。

谁能告诉我如何使用客户端 API 将 refresh_token 换成新的 access_token?即使你知道如何用另一种语言来做,那也会有很大的帮助,因为我可以尝试 Rubyfy 它。谢谢!!

【问题讨论】:

    标签: ruby-on-rails oauth-2.0 omniauth access-token google-api-client


    【解决方案1】:

    您可能已经找到了这个,但您可以在 google 上阅读整个过程:https://developers.google.com/accounts/docs/OAuth2WebServer

    omniauth-google-oauth2 策略已经负责设置 access_type 和approval_prompt,因此获取刷新令牌只需使用grant_type=request_token 发布到https://accounts.google.com/o/oauth2/token

    这是大致我使用的代码:

    def refresh_token
      data = {
        :client_id => GOOGLE_KEY,
        :client_secret => GOOGLE_SECRET,
        :refresh_token => REFRESH_TOKEN,
        :grant_type => "refresh_token"
      }
      @response = ActiveSupport::JSON.decode(RestClient.post "https://accounts.google.com/o/oauth2/token", data)
      if @response["access_token"].present?
        # Save your token
      else
        # No Token
      end
    rescue RestClient::BadRequest => e
      # Bad request
    rescue
      # Something else bad happened
    end
    

    【讨论】:

    • brimil01,你就是男人!我什至不知道为什么这有效,而我进行 HTTP 调用的其他尝试却没有,但我已经研究了 12 个小时,所以明天我会弄清楚。你的代码完全有效,你让我免于遭受巨大的打击,非常感谢。
    • 当我发布这个时我似乎明白了:#<:response:0x7fa516a926e0 parsed_response='{"error"='>"invalid_request"}, @response=#<:httpbadrequest bad readbody="true">, @headers={"cache-control"=>["no-cache, no-store, max-age=0, must-revalidate"], "pragma"=>["no-cache" ], "expires"=>["Fri, 01 Jan 1990 00:00:00 GMT"], "date"=>["Wed, 27 Mar 2013 08:50:48 GMT"], "content-type"= >["application/json"], "x-content-type-options"=>["nosniff"], "x-frame-options"=>["SAMEORIGIN"], "x-xss-protection"=> ["1; mode=block"], "server"=>["GSE"], "connection"=>["close"]}>...
    【解决方案2】:

    既然您使用的是 Ruby Google API 客户端,为什么不使用它来交换刷新令牌呢? Ruby API 在内部做了几乎相同的事情,@brimil01 在他的回答中已经说过。

    这就是我使用 Ruby API 将刷新令牌换成新访问令牌的方式。

    def self.exchange_refresh_token( refresh_token )
      client = Google::APIClient.new
      client.authorization.client_id = CLIENT_ID
      client.authorization.client_secret = CLIENT_SECRET
      client.authorization.grant_type = 'refresh_token'
      client.authorization.refresh_token = refresh_token
    
      client.authorization.fetch_access_token!
      client.authorization
    end
    

    并且根据this issue here,建议不要使用expired?方法检查访问令牌是否过期。

    基本上不叫过期了?方法。基本上为零 这是一个好主意的场景。它根本不会给你可靠的 过期信息。这更像是一个提示而不是真正的到期 时间戳,并且令牌服务器可能会决定兑现过期的令牌 无论如何,在某些有点理论但重要的情况下。 如果您确实收到无效授权错误,请始终刷新您的访问令牌 并重试一次。如果您仍然收到错误,请提出错误。

    这就是我的工作。

    # Retrieved stored credentials for the provided user email address.
    #
    # @param [String] email_address
    #   User's email address.
    # @return [Signet::OAuth2::Client]
    #  Stored OAuth 2.0 credentials if found, nil otherwise.
    def self.get_stored_credentials(email_address)
      hash = Thread.current['google_access_token']
      return nil if hash.blank?
    
      hash[email_address]
    end
    
    ##
    # Store OAuth 2.0 credentials in the application's database.
    #
    # @param [String] user_id
    #   User's ID.
    # @param [Signet::OAuth2::Client] credentials
    #   OAuth 2.0 credentials to store.
    def self.store_credentials(email_address, credentials)
      Thread.current['google_access_token'] ||= {}
      Thread.current['google_access_token'][email_address] = credentials
    end
    
    
    def self.credentials_expired?( credentials )
      client = Google::APIClient.new
      client.authorization = credentials
      oauth2 = client.discovered_api('oauth2', 'v2')
      result = client.execute!(:api_method => oauth2.userinfo.get)
    
      (result.status != 200)
    end
    
    
    # @return [Signet::OAuth2::Client]
    #  OAuth 2.0 credentials containing an access and refresh token.
    def self.get_credentials
      email_address = ''
    
      # Check if a valid access_token is already available.
      credentials = get_stored_credentials( email_address )
      # If not available, exchange the refresh_token to obtain a new access_token.
    
      if credentials.blank?
        credentials = exchange_refresh_token(REFRESH_TOKEN)
        store_credentials(email_address, credentials)
      else
        are_credentials_expired = credentials_expired?(credentials)
    
        if are_credentials_expired
          credentials = exchange_refresh_token(REFRESH_TOKEN)
          store_credentials(email_address, credentials)
        end
      end
    
      credentials
    end
    

    【讨论】:

    • credentials_expired? 中,对client.execute 的调用不应使用execute! 表单,因为如果凭据过期而不是使用未授权状态填充result,它将引发异常。
    【解决方案3】:

    我用下面的简单代码修复了它。

       def refesh_auth_tooken(refresh_token) 
           client = Google::APIClient.new 
           puts "REFESH TOOKEN"
           client.authorization = client_secrets
           client.authorization.refresh_token = refresh_token
    
           #puts YAML::dump(client.authorization)
    
           client.authorization.fetch_access_token!
           return client.authorization
    
         end 
    

    【讨论】:

      猜你喜欢
      • 2019-03-16
      • 2020-12-11
      • 2014-12-04
      • 2018-01-25
      • 1970-01-01
      • 2019-06-29
      • 2014-10-12
      • 2021-12-17
      • 2014-02-10
      相关资源
      最近更新 更多