目录
首先,在Google API控制台上为您的网站注册并启用Google API访问权限
下一步:使用AccessToken访问另一个Google API端点以获取用户的电子邮件
介绍
您可以使用Google OAuth登录做很多事情。
然而,在本文中,我想重点介绍一个基本功能:使用Google OAuth处理您网站的用户登录。
让我们开始吧。
有关更多详细信息,请参阅官方Google文档:
首先,在Google API控制台上为您的网站注册并启用Google API访问权限
Google API控制台:https://console.developers.google.com/apis/library
步骤 1:创建新项目。
步骤 2:设置OAuth同意屏幕。
按照屏幕上的说明填写应用的详细信息。
步骤 3:范围
单击添加或删除范围。在这里,我们将只选择一个范围:
../auth/userinfo.email
步骤 4:测试用户
添加一些用户用于测试目的。
步骤 5:创建凭据
导航到 API和服务>凭据> OAuth客户端ID >创建凭据:
选择“应用程序类型”作为“Web应用程序”。
填写“授权的JavaScript源”和“授权的重定向URI”。
这两个URL至关重要。请确保稍后在代码中使用这些确切的URL。
授权重定向URI 应是Google API在成功登录后将值返回到您的网站的确切目标网址。
步骤 6:获取客户端ID和客户端密钥
创建OAuth客户端后,复制客户端ID 和客户端密码,因为稍后在代码中将需要它们。
建立网站
要开始Google OAuth登录,请首先准备以下参数:
- access_type=”online” (此选项可以更改为“离线”,本文后面将对此进行讨论)
- client_id= <your Google client id>——在Google API注册期间获得
- redirect_uri= Google登录后的目标重定向页面——与您在Google API上注册的内容完全相同
- response_type=”code”
- scope=”email”—— Google API注册期间定义的允许范围
- prompt=”consent”——通知用户应用程序请求的权限
- login_hint——(可选)如果您的应用程序知道哪个用户正在尝试进行身份验证,则可以使用此参数向Google身份验证服务器提供提示。服务器使用此提示来简化登录流程,方法是在登录表单中预先填写电子邮件字段,或者选择适当的多点登录会话。
需要上述参数才能传递到Google OAuth登录页面或API端点,即:
https://accounts.google.com/o/oauth2/v2/auth
有多种方法可以启动Google OAuth登录。最简单的方法之一是在您的登录页面上包含一个链接,将用户重定向到Google OAuth登录页面。将所有参数附加为查询字符串。
使用HTML <a>标记(用于文档目的的换行符):
<a href="https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id=xxxxx
&redirect_uri=https%3A//mywebsite.com/oauth
&response_type=code
&scope=email
&prompt=consent">Sign In With Google</a>
或者,使用JavaScript:
<button type="button" onclick="signInWithGoogle();">Sign In With Google</button>
<script>
function signInWithGoogle() {
const clientId = 'xxxxxxxxxxxxxxxxxxxxxxxx';
const redirectUri = encodeURIComponent('https://mywebsite.com/oauth');
const url = `https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id=${clientId}
&redirect_uri=${redirectUri}
&response_type=code
&scope=email
&prompt=consent`;
window.location.href = url;
}
</script>
或者,从后端使用C#重定向(用于文档目的的换行符):
public static void SignInWithGoogle()
{
string clientId = "xxxxxxxxxxxxxxxxxxxxxxxx";
string redirectUri = HttpUtility.UrlEncode("https://mywebsite.com/oauth");
string url = $@"https://accounts.google.com/o/oauth2/v2/auth?
access_type=online
&client_id={clientId}
&redirect_uri={redirectUri}
&response_type=code
&scope=email
&prompt=consent";
HttpContext.Current.Response.Redirect(url, true);
}
获取授权码
用户在Google登录页面上成功登录后,Google会将他们连同一些参数(查询string)重定向回您的网站。
目标重定向URL的示例为:
https://mywebsite.com/oauth
or
https://mywebsite.com/login
在ASP.NET WebForms中,物理页以文件扩展名.aspx结尾,物理路径可能如下所示:
https://mywebsite.com/oauth.aspx
or
https://mywebsite.com/pages/user/login/google-login.aspx
可以执行路由。创建或打开Global.asax文件并添加URL路由:
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("oauth", "oauth", "~/oauth.aspx");
// or
RouteTable.Routes.MapPageRoute("google-login",
"pages/user/login/google-login", "~/oauth.aspx");
}
我写了一篇关于在一行中一次路由所有页面的文章,这是链接:
https://adriancs.com/aspnet-webforms/419/automatic-route-all-pages-in-asp-net-webforms/
**注意:Google API无需使用路由即可正常工作;如果您愿意,您仍然可以使用绝对文件路径。例如:
https://mywebsite.com/login.aspx
https://mywebsite.com/oauth.aspx
https://mywebsite.com/google-login.aspx
以下参数将作为查询字符串一起返回到您的网站:
- code=<授权码>
- scope=<允许您从Google用户那里访问的数据>
- authuser=<在当前浏览器上登录的用户的索引号>
- prompt = “consent”<用户将被告知您的应用正在请求的权限>
完整URL示例(用于文档目的的换行符):
https://mywebsite.com/oauth?
code=xxxxxxx
&scope=email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid
&authuser=0
&prompt=consent
在后端获取授权码
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Request.QueryString["code"] != null)
{
string authorizationCode = Request.QueryString["code"] + "";
}
}
}
获取OAuth 2.0访问令牌
准备以下参数:
- client_id=您的Google客户端ID
- client_secret=您的Google客户端密钥
- code=在上一步中获取的授权码
- grant_type = “authorization_code”(固定字符串)
- redirect_uri= Google登录期间使用的重定向网址
- access_type = “online”(固定字符串)
**注意:access_type还有另一个选项值,即“offline”。这将在本文的最后一部分进行讨论。
这些参数将发送到Google的OAuth 2.0端点以获取访问令牌:
https://oauth2.googleapis.com/token
可以使用POST或GET请求发送参数。
发送POST请求的示例:
using System.Net.Http;
public async Task GetAccessTokenAsync()
{
string url = "https://oauth2.googleapis.com/token";
var dicData = new Dictionary<string, string>();
dicData["client_id"] = google_api_client_id;
dicData["client_secret"] = google_api_client_secret;
dicData["code"] = authorization_code;
dicData["grant_type"] = "authorization_code";
dicData["redirect_uri"] = google_api_redirect_url;
dicData["access_type"] = "online";
try
{
using (var client = new HttpClient())
using (var content = new FormUrlEncodedContent(dicData))
{
HttpResponseMessage response = await client.PostAsync(url, content);
string json = await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex)
{
// error
}
}
发送GET请求的示例(使用string插值构造的 url):
public async Task GetAccessTokenAsync()
{
string baseUrl = "https://oauth2.googleapis.com/token";
string encodedRedirectUri = HttpUtility.UrlEncode(google_api_redirect_url);
string urlWithParameters = $"{baseUrl}?client_id={google_api_client_id}&
client_secret={google_api_client_secret}&code={authorizationCode}&
grant_type=authorization_code&redirect_uri={encodedRedirectUri}&access_type=online";
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(urlWithParameters);
json = await response.Content.ReadAsStringAsync();
}
}
发送GET请求的示例(使用Dictionary/ NameValueCollection构造的url):
var dicData = new Dictionary<string, string>()
{
{ "client_id", google_api_client_id },
{ "client_secret", google_api_client_secret },
{ "code", authorizationCode },
{ "grant_type", "authorization_code" },
{ "redirect_uri", google_api_redirect_url },
{ "access_type", "online" }
};
string baseUrl = "https://oauth2.googleapis.com/token";
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (var pair in dicData)
{
query[pair.Key] = pair.Value;
}
string urlWithParameters = $"{baseUrl}?{query.ToString()}";
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(urlWithParameters);
json = await response.Content.ReadAsStringAsync();
}
Google将以JSON格式返回结果。
成功请求示例:
{
"access_token": "xxxxxxxxxxx",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxxxx"
}
错误请求示例:
// example 1:
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
// example 2:
{
"error": "redirect_uri_mismatch",
"error_description": "Bad Request"
}
生成C#类对象以存储响应信息:
public class OAuthTokenResponse
{
public string access_token { get; set; }
public int expires_in { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
public string token_type { get; set; }
public string id_token { get; set; }
public string error { get; set; }
public string error_description { get; set; }
public bool IsSuccess => string.IsNullOrEmpty(error);
}
然后,使用System.Text.Json将JSON转换为类对象:
using System.Text.Json;
OAuthTokenResponse tokenResponse = JsonSerializer.Deserialize<OAuthTokenResponse>(json);
string AccessToken = "";
if (tokenResponse.IsSuccess)
{
// success
AccessToken = tokenResponse.access_token;
}
else
{
// error
}
已获取访问令牌。您可以使用此令牌访问或保存用户Google帐户中的数据,例如阅读或发送电子邮件、访问或保存日历事件、获取Google云端硬盘中的文件列表等。
在本文中,我们只对获取用户的电子邮件地址感兴趣。访问令牌的生存期为一小时,足以从Google API检索用户的电子邮件地址。在我们的例子中,访问令牌将只使用一次。
下一步:使用AccessToken访问另一个Google API端点以获取用户的电子邮件
此操作是使用GET请求完成的。
使用带有授权请求标头的GET请求的示例(推荐,更安全):
using System.Net.Http;
using System.Net.Http.Headers;
public async Task GetEmail()
{
string json = "";
string url = $"https://www.googleapis.com/oauth2/v2/userinfo?fields=email";
using (var client = new HttpClient())
{
// set the access token at the request header
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", AccessToken);
HttpResponseMessage response = await client.GetAsync(url);
json = await response.Content.ReadAsStringAsync();
}
}
将GET请求与在查询字符串中发送的访问令牌一起使用的示例(安全性较低):
public async Task GetEmail()
{
string json = "";
string url = $"https://www.googleapis.com/oauth2/v2/userinfo?fields=email&oauth_token={AccessToken}";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url);
json = await response.Content.ReadAsStringAsync();
}
}
JSON返回内容:
成功请求示例:
{
"email": "somebody@gmail.com"
}
失败请求示例:
{
"error": {
"code": 401,
"message": "Request is missing required authentication credential.
Expected OAuth 2 access token, login cookie or other valid authentication credential.
See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
结合JSON的两个结果,我们可以创建一个C#类Object来处理这两种情况:
public class ApiEmailResponse
{
public string email { get; set; }
public ApiError error { get; set; }
public bool IsSuccess => error == null;
public class ApiError
{
public int code { get; set; }
public string message { get; set; }
public string status { get; set; }
}
}
将JSON转换为类对象:
ApiResponse emailResponse = JsonSerializer.Deserialize<ApiEmailResponse>(json);
string UserEmail = "";
if (emailResponse.IsSuccess)
{
// success
UserEmail = emailResponse.email;
}
else
{
// failed
}
此时已成功获取用户的电子邮件。
在结束本文之前,让我们更深入地研究一下“访问令牌”。
访问令牌是一种数字密钥,它使应用能够访问用户的帐号或Google服务上的数据,而无需用户的密码。如前所述,访问令牌的生命周期为一小时。过期后,您的应用程序必须重复Google登录过程。如果我们唯一关心的是获取用户的电子邮件,那么这个持续时间就足够了。但是,对于开发第三方应用程序(例如访问用户的Google日历或Google云端硬盘的应用程序),每小时提示用户登录一次是不切实际的。
请记住,在启动Google登录时,有一个参数access_type=online?
如果将access_type设置为offline,您将获得另一个附加参数refresh_token:
{
"access_token": "xxxxxxxxxxx",
"expires_in": 3599,
"refresh_token": "xxxxxxxxxx",
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxxxx"
}
expires_in表示access_token的剩余有效时间。缓存此值,一旦它即将过期,或者当access_token无法从Google访问数据时,使用refresh_token获取新的access_token。
string AccessToken = "";
async void RenewAccessToken()
{
string url = "https://oauth2.googleapis.com/token";
var dicData = new Dictionary<string, string>()
{
{ "client_id", google_api_client_id },
{ "client_secret", google_api_client_secret },
{ "refresh_token", RefreshToken },
{ "grant_type", "refresh_token" }
};
string url = "https://oauth2.googleapis.com/token";
var content = new FormUrlEncodedContent(dicData);
string json = "";
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.PostAsync(url, content);
json = await response.Content.ReadAsStringAsync();
var tokenResponse =
JsonSerializer.Deserialize<OAuthTokenResponse>(json, jsonOptions);
AccessToken = tokenResponse.access_token;
}
}
这是返回结果(JSON)的示例:
{
"access_token": "xxxxxxx",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/userinfo.email openid",
"token_type": "Bearer",
"id_token": "xxxxxxxxxxxxxx"
}
你有它,新的access_token。
现在,在您的网站上,通过使用获得的电子邮件地址,您可以继续创建一个新的用户帐户或登录用户(如果已经创建了用户帐户)。
在您的Web应用程序中,可能有一些方法可以创建会话令牌并使用现有Cookie实现自动登录。
当用户首次登录您的网站时,将创建一个新的登录会话令牌。此令牌被保存,然后发送到用户的浏览器以存储为cookie。
每次用户重新打开浏览器时,cookie都会发送回服务器。然后,服务器从数据库中检索用户信息。如果Cookie中的会话令牌与数据库中的会话令牌匹配,则执行自动登录,并延长Cookie的生存期(到期日期)。
会话Cookie通常具有到期日期。如果会话令牌Cookie过期,系统会将用户重定向到Google登录页面以重新启动身份验证过程。
有多种方法可以管理用户登录会话,这可能是另一篇文章的主题,也许是第2部分。
Google OAuth 2.0登录提供了广泛的自定义选项。如果您有任何其他想法,请随时在评论区分享。
https://www.codeproject.com/Articles/5376012/Using-Google-OAuth-2-0-as-User-Sign-In-for-ASP-NET