在服务器上为 GCP Speech to Text 生成 AccessToken 以在 Android/iOS 中使用

Posted

技术标签:

【中文标题】在服务器上为 GCP Speech to Text 生成 AccessToken 以在 Android/iOS 中使用【英文标题】:Generate AccessToken for GCP Speech to Text on server for use in Android/iOS 【发布时间】:2020-01-17 20:42:54 【问题描述】:

从事将 Google Cloud 的语音到文本 api 集成到 androidios 环境中的项目。浏览提供的示例代码 (https://cloud.google.com/speech-to-text/docs/samples) 并能够让它运行。使用它们作为模板将语音添加到我的应用程序中,但是样本中存在严重危险,特别是在生成 AccessToken(Android sn-p 下面)时:

// ***** WARNING *****
// In this sample, we load the credential from a JSON file stored in a raw resource
// folder of this client app. You should never do this in your app. Instead, store
// the file in your server and obtain an access token from there.
// *******************
final InputStream stream = getResources().openRawResource(R.raw.credential);
try 
   final GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
      .createScoped(SCOPE);
   final AccessToken token = credentials.refreshAccessToken();

在本地开发和测试这很好,但正如评论所指出的,将凭证文件保存到生产应用程序版本中是不安全的。所以我需要做的是用来自服务器端点的请求替换这段代码。此外,我需要编写将接受请求并传回令牌的端点。虽然我发现了一些与 Firebase Admin 库生成令牌相关的非常有趣的教程,但我找不到任何与为 GCP api 执行类似操作相关的内容。

感谢任何可以为我指明正确方向的建议/文档/示例!

注意:服务器端点将是 Node.js 环境。

【问题讨论】:

【参考方案1】:

抱歉耽搁了,我能够让这一切一起工作,现在我只是回过头来发布一个极其简化的操作方法。首先,我在服务器端点项目https://www.npmjs.com/package/google-auth-library上安装了以下库

为了简单起见,在这种情况下,服务器端点缺少任何身份验证/授权等。我会把这部分留给你。我们还将假装这个端点可以从https://www.example.com/token访问

期望调用https://www.example.com/token 会得到一个带有字符串令牌的响应、一个过期数字以及一些关于令牌生成方式的额外信息:

即:

"token":"sometoken", "expires":1234567, "info": ... additional stuff

在这个例子中,我使用了一个 ServiceAccountKey 文件,该文件将存储在服务器上, 建议的路线是设置服务器环境变量并使用https://cloud.google.com/docs/authentication/production#finding_credentials_automatically,但这是为了示例,并且很容易进行快速测试。这些文件如下所示:(荣誉系统不会窃取我的私钥)

ServiceAccountKey.json


  "type": "service_account",
  "project_id": "project-id",
  "private_key_id": "378329234klnfgdjknfdgh9fgd98fgduiph",
  "private_key": "-----BEGIN PRIVATE KEY-----\nThisIsTotallyARealPrivateKeyPleaseDontStealIt=\n-----END PRIVATE KEY-----\n",
  "client_email": "project-id@appspot.gserviceaccount.com",
  "client_id": "12345678901234567890",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/project-id%40appspot.gserviceaccount.com"

所以这里是一个简单的端点,它会输出一个 AccessToken 和一个指示令牌何时过期的数字(这样您以后可以调用一个新的)。

endpoint.js

const express = require("express");
const auth = require("google-auth-library");
const serviceAccount = require("./ServiceAccountKey.json");

const googleauthoptions = 
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
    credentials: serviceAccount
;

const app = express();
const port = 3000;
const auth = new auth.GoogleAuth(googleauthoptions);
auth.getClient().then(client => 
    app.get('/token', (req, res) => 
        client
            .getAccessToken()
            .then((clientresponse) => 
            if (clientresponse.token) 
                return clientresponse.token;
            
            return Promise.reject('unable to generate an access token.');
        )
            .then((token) => 
            return client.getTokenInfo(token).then(info => 
                const expires = info.expiry_date;
                return res.status(200).send( token, expires, info );
            );
        )
            .catch((reason) => 
            console.log('error:  ' + reason);
            res.status(500).send( error: reason );
        );
    );
    app.listen(port, () => 
        console.log(`Server is listening on https://www.example.com:$port`);
    );
    return;
);

现在差不多完成了,将以android为例。第一个剪辑将是它最初从设备文件中提取的方式:

public static final List<String> SCOPE = Collections.singletonList("https://www.googleapis.com/auth/cloud-platform");
    final GoogleCredentials credentials = GoogleCredentials.fromStream(this.mContext.getResources().openRawResource(R.raw.credential)).createScoped(SCOPE);
      final AccessToken token = credentials.refreshAccessToken();
final string token = accesstoken.getTokenValue();
final long expires = accesstoken.getExpirationTime().getTime()
final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
    prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
    fetchAccessToken();

现在我们通过互联网从端点获取令牌(未显示),手头有令牌和过期信息,我们以与在设备上生成相同的方式处理它:

//
// lets pretend endpoint contains the results from our internet request against www.example.com/token
final string token = endpoint.token;
final long expires = endpoint.expires
    final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
    prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
    fetchAccessToken();

无论如何,如果有人有类似的需求,希望这会有所帮助。

===== 回复:AlwaysLearning 评论部分 =====

与基于原始文件凭据的解决方案相比: https://github.com/GoogleCloudPlatform/android-docs-samples/blob/master/speech/Speech/app/src/main/java/com/google/cloud/android/speech/SpeechService.java

在我的具体情况下,我通过 react-native 环境(位于 android 之上并使用 javascript)与与 google 无关的安全 api 端点进行交互。

我已经有了与我创建的 api 端点安全通信的机制。

所以我在概念上调用 react native

MyApiEndpoint()

这给了我一个令牌/过期即。

token = "some token from the api" // token info returned from the api
expires = 3892389329237  // expiration time returned from the api

然后我将该信息从 react-native 传递到 java,并通过此函数使用存储的信息更新 android 首选项(我将此函数添加到 SpeechService.java 文件中)

  public void setToken(String value, long expires) 
    final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
    prefs.edit().putString(PREF_ACCESS_TOKEN_VALUE, value).putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, expires).apply();
    fetchAccessToken();
  

此函数将令牌和过期内容添加到众所周知的共享偏好位置并启动 AccessTokenTask()

AccessTokenTask 被修改为简单地从首选项中提取

private class AccessTokenTask extends AsyncTask<Void, Void, AccessToken> 
protected AccessToken doInBackground(Void... voids) 
      final SharedPreferences prefs = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
      String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null);
      long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1);
      if (tokenValue != null && expirationTime != -1) 
        return new AccessToken(tokenValue, new Date(expirationTime));
      
      return null;
    

你可能会注意到我在这里对过期信息没有做太多,我在其他地方检查过期信息。

【讨论】:

假设我使用这种方法获取令牌并将其发送到前端。如何在前端对 API 进行身份验证?我在文档中看到的唯一身份验证方法是使用默认凭据文件... 嗨 AlwaysLearning,我设法错过了您的评论,它可能在您需要它的时候已经过去了,但它是:我最初正在使用此处找到的示例代码:github.com/GoogleCloudPlatform/android-docs-samples/tree/master/… 虽然我会编辑上面的帖子,但第二次我的回答会比评论更容易涉及。【参考方案2】:

这里有几个有用的链接:

Importing the Google Cloud Storage Client library in Node.js Cloud Storage authentication

【讨论】:

你好哈维尔,当然好的链接,我可能应该添加我在原始帖子中查看的链接。但他们并没有真正描述用例。我查看的大多数文档都与使用应用程序默认凭据 (ADC) cloud.google.com/docs/authentication/… 的服务器有关,然后将服务器与服务器通信。在这种情况下,我想在服务器上生成一个令牌,将令牌传递给设备,让设备与谷歌 api 通信。

以上是关于在服务器上为 GCP Speech to Text 生成 AccessToken 以在 Android/iOS 中使用的主要内容,如果未能解决你的问题,请参考以下文章

在 Xamarin Forms App 中尝试 Speech-To-Text 后,Text-To-Speech 播放的音量非常低

Google Cloud - Speech to Text 用户配额

IBM Cloud Speech to Text 语音识别

Javascript 中的 Watson Text to Speech 的授权问题

如何从 Google Apps 脚本授权 Google Speech-to-text?

在 Python 中使用 Microsoft Azure Speech-to-text 的字幕/说明文字