我尝试在我的nodejs上使用(DialogFlow或API.ai和Google操作)开发的Google智能助理应用程序实施oauth2身份验证。
所以我跟着这个answer。但我总是得到"看起来您的测试oauth帐户尚未关联。 "错误。当我尝试打开调试选项卡上显示的URL时,它显示500个已损坏的URL错误。
Dialogflow fullfillment
index.js
'use strict';
const functions = require('firebase-functions'); // Cloud Functions for Firebase library
const DialogflowApp = require('actions-on-google').DialogflowApp; // Google Assistant helper library
const googleAssistantRequest = 'google'; // Constant to identify Google Assistant requests
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
console.log('Request headers: ' + JSON.stringify(request.headers));
console.log('Request body: ' + JSON.stringify(request.body));
// An action is a string used to identify what needs to be done in fulfillment
let action = request.body.result.action; // https://dialogflow.com/docs/actions-and-parameters
// Parameters are any entites that Dialogflow has extracted from the request.
const parameters = request.body.result.parameters; // https://dialogflow.com/docs/actions-and-parameters
// Contexts are objects used to track and store conversation state
const inputContexts = request.body.result.contexts; // https://dialogflow.com/docs/contexts
// Get the request source (Google Assistant, Slack, API, etc) and initialize DialogflowApp
const requestSource = (request.body.originalRequest) ? request.body.originalRequest.source : undefined;
const app = new DialogflowApp({request: request, response: response});
// Create handlers for Dialogflow actions as well as a 'default' handler
const actionHandlers = {
// The default welcome intent has been matched, welcome the user (https://dialogflow.com/docs/events#default_welcome_intent)
'input.welcome': () => {
// Use the Actions on Google lib to respond to Google requests; for other requests use JSON
//+app.getUser().authToken
if (requestSource === googleAssistantRequest) {
sendGoogleResponse('Hello, Welcome to my Dialogflow agent!'); // Send simple response to user
} else {
sendResponse('Hello, Welcome to my Dialogflow agent!'); // Send simple response to user
}
},
// The default fallback intent has been matched, try to recover (https://dialogflow.com/docs/intents#fallback_intents)
'input.unknown': () => {
// Use the Actions on Google lib to respond to Google requests; for other requests use JSON
if (requestSource === googleAssistantRequest) {
sendGoogleResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
} else {
sendResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
}
},
// Default handler for unknown or undefined actions
'default': () => {
// Use the Actions on Google lib to respond to Google requests; for other requests use JSON
if (requestSource === googleAssistantRequest) {
let responseToUser = {
//googleRichResponse: googleRichResponse, // Optional, uncomment to enable
//googleOutputContexts: ['weather', 2, { ['city']: 'rome' }], // Optional, uncomment to enable
speech: 'This message is from Dialogflow\'s Cloud Functions for Firebase editor!', // spoken response
displayText: 'This is from Dialogflow\'s Cloud Functions for Firebase editor! :-)' // displayed response
};
sendGoogleResponse(responseToUser);
} else {
let responseToUser = {
//richResponses: richResponses, // Optional, uncomment to enable
//outputContexts: [{'name': 'weather', 'lifespan': 2, 'parameters': {'city': 'Rome'}}], // Optional, uncomment to enable
speech: 'This message is from Dialogflow\'s Cloud Functions for Firebase editor!', // spoken response
displayText: 'This is from Dialogflow\'s Cloud Functions for Firebase editor! :-)' // displayed response
};
sendResponse(responseToUser);
}
}
};
// If undefined or unknown action use the default handler
if (!actionHandlers[action]) {
action = 'default';
}
// Run the proper handler function to handle the request from Dialogflow
actionHandlers[action]();
// Function to send correctly formatted Google Assistant responses to Dialogflow which are then sent to the user
function sendGoogleResponse (responseToUser) {
if (typeof responseToUser === 'string') {
app.ask(responseToUser); // Google Assistant response
} else {
// If speech or displayText is defined use it to respond
let googleResponse = app.buildRichResponse().addSimpleResponse({
speech: responseToUser.speech || responseToUser.displayText,
displayText: responseToUser.displayText || responseToUser.speech
});
// Optional: Overwrite previous response with rich response
if (responseToUser.googleRichResponse) {
googleResponse = responseToUser.googleRichResponse;
}
// Optional: add contexts (https://dialogflow.com/docs/contexts)
if (responseToUser.googleOutputContexts) {
app.setContext(...responseToUser.googleOutputContexts);
}
app.ask(googleResponse); // Send response to Dialogflow and Google Assistant
}
}
// Function to send correctly formatted responses to Dialogflow which are then sent to the user
function sendResponse (responseToUser) {
// if the response is a string send it as a response to the user
if (typeof responseToUser === 'string') {
let responseJson = {};
responseJson.speech = responseToUser; // spoken response
responseJson.displayText = responseToUser; // displayed response
response.json(responseJson); // Send response to Dialogflow
} else {
// If the response to the user includes rich responses or contexts send them to Dialogflow
let responseJson = {};
// If speech or displayText is defined, use it to respond (if one isn't defined use the other's value)
responseJson.speech = responseToUser.speech || responseToUser.displayText;
responseJson.displayText = responseToUser.displayText || responseToUser.speech;
// Optional: add rich messages for integrations (https://dialogflow.com/docs/rich-messages)
responseJson.data = responseToUser.richResponses;
// Optional: add contexts (https://dialogflow.com/docs/contexts)
responseJson.contextOut = responseToUser.outputContexts;
response.json(responseJson); // Send response to Dialogflow
}
}
});
// Construct rich response for Google Assistant
const app = new DialogflowApp();
const googleRichResponse = app.buildRichResponse()
.addSimpleResponse('This is the first simple response for Google Assistant')
.addSuggestions(
['Suggestion Chip', 'Another Suggestion Chip'])
// Create a basic card and add it to the rich response
.addBasicCard(app.buildBasicCard(`This is a basic card. Text in a
basic card can include "quotes" and most other unicode characters
including emoji . Basic cards also support some markdown
formatting like *emphasis* or _italics_, **strong** or __bold__,
and ***bold itallic*** or ___strong emphasis___ as well as other things
like line \nbreaks`) // Note the two spaces before '\n' required for a
// line break to be rendered in the card
.setSubtitle('This is a subtitle')
.setTitle('Title: this is a title')
.addButton('This is a button', 'https://assistant.google.com/')
.setImage('https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
'Image alternate text'))
.addSimpleResponse({ speech: 'This is another simple response',
displayText: 'This is the another simple response ' });
// Rich responses for both Slack and Facebook
const richResponses = {
'slack': {
'text': 'This is a text response for Slack.',
'attachments': [
{
'title': 'Title: this is a title',
'title_link': 'https://assistant.google.com/',
'text': 'This is an attachment. Text in attachments can include \'quotes\' and most other unicode characters including emoji . Attachments also upport line\nbreaks.',
'image_url': 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
'fallback': 'This is a fallback.'
}
]
},
'facebook': {
'attachment': {
'type': 'template',
'payload': {
'template_type': 'generic',
'elements': [
{
'title': 'Title: this is a title',
'image_url': 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
'subtitle': 'This is a subtitle',
'default_action': {
'type': 'web_url',
'url': 'https://assistant.google.com/'
},
'buttons': [
{
'type': 'web_url',
'url': 'https://assistant.google.com/',
'title': 'This is a button'
}
]
}
]
}
}
}
};
实际上我部署的代码存在于对话框流内联编辑器中。但是,不知道如何实现oauth端点,无论它应该是一个单独的云功能还是必须包含在存在的功能中。而且我对oauth授权代码流实际如何工作非常困惑。假设我们在助手应用程序上,一旦用户说"与foo app"交谈,它是否会自动打开用于oauth代码交换过程的Web浏览器?
答案 0 :(得分:5)
answer you referenced在10月25日发布了更新,表明他们已采取措施阻止您作为帐户关联的身份验证提供程序进入google.com端点。他们似乎可能已经采取其他措施来阻止以这种方式使用Google的auth服务器。
如果您使用自己的auth服务器,则错误500会指示您的oauth服务器出错,您应该检查您的oauth服务器是否有错误。
更新以回答您的其他一些问题。
但不知道如何实现oauth端点
Google使用Implicit Flow或Authorization Code Flow以及如何测试,为您提供有关最小OAuth服务所需操作的指导(但不是代码)。
是否应该是单独的云功能,或者它必须包含在现有的
中
它应该分开 - 甚至可以说它必须分开。在隐式流和授权代码流中,您需要提供一个URL端点,用户将被重定向以登录您的服务。对于授权代码流程,您还需要一个额外的webhook,助理将用它来交换令牌。
这些功能背后的功能需要与您为Dialogflow webhook所做的非常不同。虽然有人可能会创建一个处理所有不同任务的功能 - 但是没有必要。您将单独提供OAuth网址。
但是,您的Dialogflow webhook 与您的OAuth服务器有一些关系。特别是,OAuth服务器交给智能助理的令牌将被交还给Dialogflow webhook,因此Dialogflow需要某种方式来获取基于该令牌的用户信息。有很多方法可以做到这一点,但只列出几个:
令牌可以是JWT,并包含用户信息作为正文中的声明。 Dialogflow webhook应该使用公钥来验证令牌是否有效,并且需要知道声明的格式。
OAuth服务器和Dialogflow webhook可以使用共享帐户数据库,OAuth服务器将令牌存储为用户帐户的密钥并删除过期的密钥。然后,Dialogflow webhook可以使用它获取的令牌作为查找用户的密钥。
OAuth服务器可能有一个(另一个)webhook,Dialogflow可以在其中请求用户信息,将密钥作为Authorization标头传递并获得回复。 (例如,Google就是这样做的。)
确切的解决方案取决于您的需求以及您可以使用的资源。
而且我对oauth授权代码流实际如何工作非常困惑。假设我们在助手应用程序上,一旦用户说"与foo app"交谈,它会自动打开一个用于oauth代码交换过程的Web浏览器吗?
从广义上讲 - 是的。细节有所不同(并且可以改变),但不要过于关注细节。
如果您在发言人上使用智能助理,系统会提示您打开主应用程序,该应用程序应显示一张卡片,说明行动需要什么权限。单击该卡将打开浏览器或webview到Actions网站以开始流程。
如果您在移动设备上使用智能助理,它会直接提示您,然后打开浏览器或网页浏览到动作网站以开始流程。
认证流程主要涉及:
在幕后,Google会使用此代码,并且由于您正在使用授权代码流,因此请在令牌交换网址上将其换成身份验证令牌和刷新令牌。
然后,只要用户使用您的操作,它就会将身份验证令牌以及请求的其余部分发送到您的服务器。
Plz建议OAuth2配置的必要包
我不能这样做。对于初学者 - 它完全取决于您的其他资源和要求。 (这就是为什么StackOverflow不喜欢人们要求这样的建议。)
有些软件包(您可以搜索它们),可以让您设置OAuth2服务器。我确定有人在那里提供OAuth-as-a-service,虽然我不知道任何随便的。最后,如上所述,您可以使用Google提供的指南编写最小的OAuth2服务器。
尝试为Google的OAuth创建代理......可能...... not as easy as it first seems ......可能不如任何人都满意的那样安全......而且可能(但不是)必然,IANAL)违反Google's Terms of Service。
我们不能通过这种方式存储用户的电子邮件地址吗?
嗯,您可以在用户的帐户中存储您想要的任何内容。但这是用户对您的操作的帐户。
例如,您可以代表您的用户访问Google API,以获取他们的电子邮件地址或他们授权您与Google合作的其他任何内容。您拥有的用户帐户可能会存储您用于访问Google服务器的OAuth令牌。但是,您应该逻辑地将其视为与Assistant用于访问服务器的代码分开。
答案 1 :(得分:0)
我对最小oauth2服务器的实现(适用于隐式流但不存储用户会话)。
取自https://developers.google.com/identity/protocols/OAuth2UserAgent。
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
//Get the state and redirect_uri parameters from the request
var searchParams = new URLSearchParams(window.location.search);
var state = searchParams.get("state");
var redirect_uri = searchParams.get("redirect_uri");
//var client_id = searchParams.get("client_id");
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
'client_id': YOUR_CLIENT_ID,
'redirect_uri': redirect_uri,
'scope': 'email',
'state': state,
'response_type': 'token',
'include_granted_scopes': 'true'
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
此实现不是很安全,但它是我作为助手的OAuth服务器工作的唯一代码。
答案 2 :(得分:0)
很长一段时间后我能够让它发挥作用。我们必须首先启用webhook,我们可以看到如何在对话框流程实现文档中启用webhook如果我们要使用Google智能助理,那么我们必须首先在集成中启用Google智能助理集成。然后按照下面提到的步骤进行谷歌操作中的帐户关联: -
转到Google云端控制台 - > API和服务 - >凭证 - > OAuth 2.0客户端ID - >网络客户端 - >请注意客户端ID,客户端密码 - >下载JSON - 从json记下项目id,auth_uri,token_uri - >授权重定向URI - >白名单我们的应用网址 - >在此网址中,已修复的部分为https://oauth-redirect.googleusercontent.com/r/,并将项目ID附加到网址中 - >保存更改
Google上的操作 - >帐户链接设置1.授予类型=授权代码2.客户信息1.填写客户端ID,客户端secrtet,auth_uri,token_uri 2.输入auth uri为https://www.googleapis.com/auth,token_uri为https://www.googleapis.com/token 3.保存在谷歌助手上运行时会显示错误,但不要担心5.回到助手设置中的帐户链接部分,输入auth_uri作为https://accounts.google.com/o/oauth2/auth,将token_uri作为https://accounts.google.com/o/oauth2/token输入6将范围设为https://www.googleapis.com/auth/userinfo.profile和https://www.googleapis.com/auth/userinfo.email,我们就可以了。 7.保存更改。
在托管服务器(heroku)日志中,我们可以看到访问令牌值,通过访问令牌,我们可以获取有关电子邮件地址的详细信息。
将访问令牌附加到此链接" https://www.googleapis.com/oauth2/v1/userinfo?access_token="我们可以在生成的json页面中获取所需的详细信息。
`accessToken = req.get("originalRequest").get("data").get("user").get("accessToken")
r = requests.get(link)
print("Email Id= " + r.json()["email"])
print("Name= " + r.json()["name"])`