客户端未经授权使用此方法检索访问令牌服务帐户错误

Posted

技术标签:

【中文标题】客户端未经授权使用此方法检索访问令牌服务帐户错误【英文标题】:client is unauthorized to retrieve access tokens using this method service account error 【发布时间】:2019-04-25 18:40:18 【问题描述】:

让我的服务帐户在同一个 Web 应用程序上进行身份验证是一件非常头疼的事情,我也有用户通过 oauth2 登录。

所以我想知道,这甚至可能吗?

如果不是,是否应该坚持使用服务帐户?是否必须自行验证用户身份 - 老派风格?哈哈

谢谢。

关于服务帐户,我已经启用了域范围的delegation,在我的 G 套件管理控制台中启用了客户端密钥 + api 范围,并且已经获得了可以运行 books api 的 php 示例。但是,每当我尝试除书籍以外的任何其他 api 时,都会出现错误,

客户端未经授权无法使用此方法检索访问令牌

更新:我尝试使用@dalmto 的示例,并添加了几行来测试gmail api,例如:

putenv('GOOGLE_APPLICATION_CREDENTIALS=credentials.json');

$user = 'email@domain.de';

function getGoogleClient() 
    return getServiceAccountClient();


function getServiceAccountClient() 
    try 
        // Create and configure a new client object.
        $client2 = new Google_Client();
        $client2->useApplicationDefaultCredentials();
        $client2->setScopes(array('https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/admin.directory.user.readonly','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/calendar'));
        $client2->setAccessType('offline');
        $client2->setSubject($user);
        return $client2;
     catch (Exception $e) 
        print "An error occurred: " . $e->getMessage();
    


$newGoogleClient = getGoogleClient();

$service3 = new Google_Service_Gmail($newGoogleClient);
$results3 = $service3->users_labels->listUsersLabels($user);

但我现在只收到“400:错误请求”错误

编辑:经过更多挖掘后,有一条注释:'failedPrecondition' - 知道可能是哪个前提条件吗?我在管理控制台中为客户端允许了以下范围:

hxxps://www.googleapis.com/auth/gmail.metadata, hxxps://www.googleapis.com/auth/userinfo.email, hxxps://www.googleapis.com/auth/userinfo.profile, hxxps://www.googleapis.com/auth/gmail.modify, hxxps://www.googleapis.com/auth/gmail.readonly, hxxps://www.googleapis.com/auth/gmail.labels, hxxps://mail.google.com/

并启用 api 并在“OAuth 同意屏幕”中启用范围

DWD 也已启用:Service Account Overview Screenshot

EDIT2:好的,我发现缺少的先决条件是“setSubject”。

一旦我补充说它更进一步,但仍然在'"error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method.' 再次失败

仅供参考:创建服务帐户时,我赋予它“项目 -> 所有者”角色。这足够了吗?是否需要添加更多?

EDIT3:我也刚刚检查了记录器,它说 DWD 已启用..我的想法到此结束,哈哈

   client: 
    adminState: 
     updateTime:  "2018-11-23T00:29:44.810Z"      
    
    assertionMatchExistingGrant:  "MATCH_GRANT_DISABLED"     
    authType:  "PUBLIC_KEY"     
    brandId:  "aaaaaaaaaaaaaa"     
    clientId:  "aaaaaaaaaaaaaaaaaa"     
    consistencyToken:  "2018-11-23T00:29:44.953175Z"     
    creationTime:  "2018-11-23T00:29:44.810Z"     
    displayName:  "Client for servicemaint1"     
    domainWideDelegation:  "DELEGATION_ENABLED"     
    projectNumber:  "aaaaaaaaaaaaaaaa"     
    threeLeggedOauth:  "DISABLED"     
    updateTime:  "2018-11-23T00:29:44.953175Z"     
   

EDIT4:终于可以工作了!

所以我一直在一个新项目中尝试这个,我为整个早上/昨晚的测试而创建。但是我的 oauth2 用户身份验证是通过一个不同的项目运行的(昨天早上/下午我也无法让服务帐户正常工作)。

所以无论如何,我注意到:https://myaccount.google.com/permissions“有权访问您帐户的应用程序” - 只有我的旧项目/应用程序被授权。所以我切换回我的第一个项目,创建了一个新的服务帐户客户端 ID .json 文件,它终于可以对两者进行身份验证了! :)

我必须在某个额外的地方获得授权,这是我在第二个项目中没有做过的。

再次感谢。

EDIT5: 还有一个快速的问题 - 这是在 *** 上执行此操作的正确方法吗?不断返回编辑?

对于其他人后来偶然发现,这是我的总身份验证块(抱歉有点长):

putenv('GOOGLE_APPLICATION_CREDENTIALS=maintenanceapp.json');

$user = 'xyz@abc.com';

function getGoogleClient() 
    return getServiceAccountClient();


function getServiceAccountClient() 
  $user = 'xyz@abc.com';
    try 
        // Create and configure a new client object.
        $client2 = new Google_Client();
        $client2->useApplicationDefaultCredentials();
        $client2->setScopes(['https://www.googleapis.com/auth/gmail.metadata','https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.modify','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/gmail.labels']);
        //$client2->setAccessType('offline');
        $client2->setSubject($user);
        return $client2;
     catch (Exception $e) 
        echo "An error occurred: " . $e->getMessage();
    


$newGoogleClient = getGoogleClient();

$service3 = new Google_Service_Gmail($newGoogleClient);
$results3 = $service3->users_labels->listUsersLabels($user);


 /*************************************************
  * Ensure you've downloaded your oauth credentials
  ************************************************/
 if (!$oauth_credentials = getOAuthCredentialsFile()) 
   echo missingOAuth2CredentialsWarning();
   return;
 
 /************************************************
  * NOTICE:
  * The redirect URI is to the current page, e.g:
  * http://localhost:8080/idtoken.php
  ************************************************/
 $redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
 $client = new Google_Client();
 // USER AUTH
 $client->setAuthConfig($oauth_credentials);
 $client->setRedirectUri($redirect_uri);
 $client->setScopes(array('https://www.googleapis.com/auth/userinfo.email','https://www.googleapis.com/auth/userinfo.profile','https://www.googleapis.com/auth/gmail.readonly','https://www.googleapis.com/auth/calendar'));
 $client->setApprovalPrompt('auto');
 $client->setAccessType('offline');
 $plus = new Google_Service_Plus($client);

 /************************************************
  * If we're logging out we just need to clear our
  * local access token in this case
  ************************************************/
 if (isset($_REQUEST['logout'])) 
   unset($_SESSION['id_token_token']);
 

 /************************************************
  * If we have a code back from the OAuth 2.0 flow,
  * we need to exchange that with the
  * Google_Client::fetchAccessTokenWithAuthCode()
  * function. We store the resultant access token
  * bundle in the session, and redirect to ourself.
  ************************************************/
 if (isset($_GET['code'])) 
   $token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
   // store in the session also
   $_SESSION['id_token_token'] = $token;
   // redirect back to the example
   header('Location: https://abc.de/index.php');
   // return;
 
 /************************************************
   If we have an access token, we can make
   requests, else we generate an authentication URL.
  ************************************************/
 if (
   !empty($_SESSION['id_token_token'])
   && isset($_SESSION['id_token_token']['id_token'])
 ) 
   $client->setAccessToken($_SESSION['id_token_token']);
  else 
   $authUrl = $client->createAuthUrl();
   //header('Location: ' . $authUrl);
 
 /************************************************
   If we're signed in we can go ahead and retrieve
   the ID token, which is part of the bundle of
   data that is exchange in the authenticate step
   - we only need to do a network call if we have
   to retrieve the Google certificate to verify it,
   and that can be cached.
  ************************************************/
 if ($client->getAccessToken()) 
   $token_data = $client->verifyIdToken();
 

【问题讨论】:

这是一个很好的问答。我们需要更多像您这样的问题,其中包含大量真实世界的细节。 【参考方案1】:

在 google 开发者控制台中,当您创建项目和凭据时,您必须选择要为哪种类型的应用程序创建哪种类型的客户端。

有几种不同的方式可以向 google 进行身份验证。

OAuth2 原生 OAuth2 网络 手机 服务帐号

使用这些客户端的代码也不同。您不能创建 Web OAuth2 客户端并将其用于要调用服务帐户的代码。

“客户端未经授权无法使用此方法检索访问令牌”。

就是这个意思。您在 Google 开发者控制台上设置的客户端不是服务帐户客户端,或者您使用的代码不适用于服务帐户客户端。

这是我的serviceaccount.php 示例。如果您的代码需要看起来像这样,并且您需要确保您在 google 开发者控制台上创建的客户端是服务帐户客户端。

require_once __DIR__ . '/vendor/autoload.php';
// Use the developers console and download your service account
// credentials in JSON format. Place the file in this directory or
// change the key file location if necessary.
putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
/**
 * Gets the Google client refreshing auth if needed.
 * Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
 * Initializes a client object.
 * @return A google client object.
 */
function getGoogleClient() 
    return getServiceAccountClient();

/**
 * Builds the Google client object.
 * Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
 * Scopes will need to be changed depending upon the API's being accessed. 
 * array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
 * List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
 * @return A google client object.
 */
function getServiceAccountClient() 
    try    
        // Create and configure a new client object.        
        $client = new Google_Client();
        $client->useApplicationDefaultCredentials();
        $client->addScope([YOUR SCOPES HERE]);
        return $client;
     catch (Exception $e) 
        print "An error occurred: " . $e->getMessage();
    

开发者控制台

在客户端下检查您正在使用的客户端是否可以在服务帐户密钥下找到。如果不是,那么它是错误的客户端类型,并且无法与您的代码一起使用。创建一个新的服务帐户客户端并使用该客户端 ID 设置域范围的委派。

【讨论】:

感谢您的评论,因此我或多或少地通过他们的示例代码进行了常规的 oauth2 网络用户身份验证。现在我正在尝试将服务帐户添加到它。请参阅我上面的编辑。 (这是在***上执行此操作的正确方法吗,我仍然感到困惑,因为我不能像这样轻松地将代码添加到cmets ..) 好的,所以我发现缺少的先决条件是“setSubject”。一旦我补充说它更进一步,但仍然在 '"error": "unauthorized_client",\n "error_description": "Client is未授权使用此方法检索访问令牌时再次失败。' 然后去谷歌开发者控制台检查你创建的客户端是服务账户客户端类型。如果您使用的是我发布的相同代码,那么它不是服务帐户客户端。创建正确的服务帐户客户端后,请下载客户端 json 文件并将其与您的代码一起使用。检查我的更新 是的,这就是我正在使用的 - 请参阅屏幕截图:i.imgur.com/leAMXZB.png 我已经调整了您的代码,以便我的服务帐户始终使用 $client2 并且我的 oauth2 用户身份验证使用 $client - 我只是检查了一下我没有意外交叉任何变量json文件明确表示:“type”:“service_account”,作为第一个值【参考方案2】:
response_type=code
client_id=348268306866-9dl0kdgn2f9bjhoge7pris1jo8u9si47.apps.googleusercontent.com
redirect_uri=https://degoo.com/me/googleoauth2callback
access_type=offline
scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/contacts.readonly
state="RedirectUrl":"/me/chooseaccount","RegisterIfNotExists":true

这就是我们所知道的。

【讨论】:

你能解释一下你的代码是做什么的吗?对于未来的用户来说,它可能会更好

以上是关于客户端未经授权使用此方法检索访问令牌服务帐户错误的主要内容,如果未能解决你的问题,请参考以下文章

客户端未经授权使用此方法检索访问令牌,或未授权客户端使用任何请求的范围

未授权客户端:客户端未授权使用此方法检索访问令牌

未经授权的谷歌访问令牌

在内部生成 laravel 护照令牌。 401错误未经授权

通过尝试从肥皂服务检索数据,HTTP 请求未经授权

使用服务帐户时如何修复 google ads api 未经授权的错误