将 AuthSub 与 .NET 客户端库搭配使用

Jeff Fisher,Google Data API 团队
2007 年 8 月

简介:为什么 AuthSub 很重要?

Google Data API(简称“GData”)的强大之处在于,它允许开发者构建与 Google 服务交互的应用。更具体地说,它们允许您访问私人用户数据以在应用中使用。借助这些 API,您可以编写应用来同步、导入、导出和以其他方式管理这些数据。虽然这些 API 赋予了您强大的能力,但您必须记住要负责任地使用它们。由于用户数据属于私密信息,因此您自然希望以安全的方式访问这些数据。其中一个关键部分是能够以安全的方式向 Google 的服务器进行身份验证。

假设您有一个很棒的新 Web 应用,希望将其与存储在 Google Web 服务中的数据相关联。现在,您需要进行身份验证才能访问此私密数据。为什么不直接使用 ClientLogin 等简单的方法?这样确实可以解决问题,但您需要处理的私密数据会更多:用户的登录凭据。ClientLogin 要求您的应用请求用户的 Google 用户名和密码。对于在用户个人机器上运行的桌面应用,这没问题,但对于基于 Web 的应用,这不太理想。除了在您自己的服务器上处理这些凭据的责任之外,或许您的一些更谨慎的用户会担心您可能会存储他们的信息。用户提出的另一个常见问题是,他们可能只希望向某个程序授予对特定服务(例如 Google 日历中的活动)的访问权限,而不希望授予对其他服务(例如 Google 文档)的访问权限。AuthSub 通过让用户通过 Google 的服务器进行身份验证来解决这两个问题,并允许您的程序仅请求所需的访问权限。

现在,您已经充分了解了 AuthSub 背后的理论,接下来该开始编码了!在本文中,我选择保持简单,在单个 ASP 页面中完成所有操作,但您应该能够轻松地将此处演示的技术集成到自己的应用中。

处理身份验证

那么,要在 Web 应用中实际使用 AuthSub,需要做些什么呢?首先,从 GData 客户端库导入一些标准内容:

<%@ Import Namespace="Google.GData.Client" %>
<%@ Import Namespace="Google.GData.Extensions" %>
<%@ Import Namespace="System.Net" %>

现在,您首先要做的是将用户引导至精心设计的网址。这样一来,Google 的服务器就可以处理身份验证,然后将用户重定向回您的网站。幸运的是,您无需手动生成此网址,因为有一些方法可以为您完成此操作。我们来看一个示例:

authSubUrl = AuthSubUtil.getRequestUrl(target, scope, secure, session);
  • target:这是一个包含 Web 应用网址的字符串。这是用户在通过身份验证后将被重定向到的位置。
  • 范围:此字符串取决于您使用的 API。它对应于 GData API 中的某个 Feed。例如,包含用户所有日历信息的 Feed 为“http://www.google.com/calendar/feeds/default/private/full”。
  • 安全:这是一个布尔值,用于告知服务器您已向 Google 注册,并将以加密方式对发送给服务器的请求进行签名。此实参通常默认为 false,尤其是在测试环境中工作时。
  • session:这是另一个布尔值,用于指示您需要的是“会话令牌”而不是“一次性令牌”。稍后,您将更清楚地了解此实参的作用。

用户点击生成的网址后,系统会将其转到 Google 账号页面,以便其登录自己的 Google 账号。然后,他们会被重定向回您在“target”变量中指定的网页,但会附加一个包含一次性令牌的查询参数“token”。通常情况下,此令牌只能使用一次。也就是说,它可用于对指定 Feed 执行一项操作。不过,如果您将“session”参数指定为 true,则可以将其换成“会话令牌”,该令牌可以重复使用,直到用户结束会话。您可以按以下方式执行此操作:

String token = Request.QueryString["token"];
Session["token"] = AuthSubUtil.exchangeForSessionToken(token, null).ToString();

在此处,您需要从查询参数中提取令牌,并将其换成“会话令牌”。然后,为了保存该令牌以供日后使用,您可以选择将其存储在 .NET 的自动 Session 数组中。当然,您也可以选择将令牌存储在数据库中。下一步是使用此令牌发出经过身份验证的请求:

GAuthSubRequestFactory authFactory = new GAuthSubRequestFactory("cl", "My-Cool-Application");
authFactory.Token = (String) Session["token"];
CalendarService service = new CalendarService(authFactory.ApplicationName);
service.RequestFactory = authFactory;

在此处,您设置了一个 CalendarService 对象,以使用 AuthSub 进行身份验证,从而与 Google Calendar API 进行交互。请注意,构造函数中用于 GAuthSubRequestFactory 的“cl”是日历的服务名称。如需了解其他服务名称,请参阅 Google Data API 常见问题解答

安全(已注册)AuthSub

如果您选择注册 Web 应用,则可以在使用 AuthSub 时启用额外的安全级别。这样一来,您就可以对代码发出的所有请求进行数字签名,从而确保他人无法使用向您发放的 AuthSub 令牌,除非他们拥有您的私钥。第一步是确保在调用 AuthSubUtil.getRequestUrl 时通过将“secure”实参设置为 true 来生成正确的 AuthSub 链接。您还需要进行另外两项代码更改:

String token = Request.QueryString["token"];
Session["token"] = AuthSubUtil.exchangeForSessionToken(token, rsaKey).ToString();

...

authFactory.PrivateKey = rsaKey;

首先,请注意,您现在将变量“rsaKey”传递给 exchangeForSessionToken 方法,而不是 null。在设置与服务的连接时,我们还会使用同一变量来设置 GAuthSubRequestFactory 的属性。“rsaKey”变量是一个 RSACryptoServiceProvider,对应于您向 Google 注册的 x509 证书的私钥组件。

生成 RSA 私钥和自签名证书可能有点令人困惑,尤其是因为 .NET 框架无法识别以 PEM 格式存储的密钥或证书。以下命令展示了如何使用 OpenSSL 工具套件生成私钥和公共证书:

openssl req -x509 -nodes -days 365 -newkey rsa:1024 -sha1 -subj \
  '/C=US/ST=CA/L=Mountain View/CN=www.example.com' -keyout \
  test_key.pem -out test_cert.pem

openssl pkcs12 -export -in test_cert.pem -inkey test_key.pem \
  -out test_cert.pfx -name "Testing Certificate"

第一步会生成一个私钥和一个公钥 X509 证书,两者均采用 PEM 格式,分别称为“test_key.pem”和“test_cert.pem”。请注意,该证书已设置为注册到位于美国加利福尼亚州山景城的“www.example.com”。请在此处替换为贵公司的相应值。“test_cert.pem”文件包含您需要在 AuthSub 注册页面上提交的信息。

第二步会根据您的私钥和证书生成 PFX 文件。此文件可以导入到 .NET 客户端库中,用于对向 GData API 发出的请求进行数字签名。以下代码展示了如何将 PFX 文件中的私钥导入到 Web 应用中:

protected AsymmetricAlgorithm getRsaKey()
{

  X509Certificate2 cert = new X509Certificate2("C:/MyAspSite/test_cert.pfx","");
  RSACryptoServiceProvider privateKey = cert.PrivateKey as RSACryptoServiceProvider;

  return privateKey;
}

此代码段定义的 getRsaKey() 函数可用于代替上面显示的“rsaKey”变量,以对 API 进行身份验证。当然,文件路径应替换为您生成的 PFX 文件的相应位置。

完整代码清单

要展示如何使用上一部分中演示的方法,最简单的方式是使用实际示例。以下示例代码是一个简单的 ASP 网页,它使用 AuthSub 对用户进行身份验证,然后输出用户的 Google 日历活动。

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<%@ Import Namespace="Google.GData.Client" %>
<%@ Import Namespace="Google.GData.Extensions" %>
<%@ Import Namespace="Google.GData.Calendar" %>
<%@ Import Namespace="System.Net" %>

<script runat="server">
    void PrintCalendar() {

        GAuthSubRequestFactory authFactory = new GAuthSubRequestFactory("cl", "TesterApp");
        authFactory.Token = (String) Session["token"];
        CalendarService service = new CalendarService(authFactory.ApplicationName);
        service.RequestFactory = authFactory;

        EventQuery query = new EventQuery();

        query.Uri = new Uri("http://www.google.com/calendar/feeds/default/private/full");

        try
        {
            EventFeed calFeed = service.Query(query);
            foreach (Google.GData.Calendar.EventEntry entry in calFeed.Entries)
            {
                Response.Write("Event: " + entry.Title.Text + "<br/>");
            }
        }
        catch (GDataRequestException gdre)
        {
            HttpWebResponse response = (HttpWebResponse)gdre.Response;
            
            //bad auth token, clear session and refresh the page
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                Session.Clear();
                Response.Redirect(Request.Url.AbsolutePath, true);
            }
            else
            {
                Response.Write("Error processing request: " + gdre.ToString());
            }
        }
    }

</script>


<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Test Site</title>
</head>
<body>

    <form id="form1" runat="server">
    <h1>AuthSub Sample Page</h1>
    <div>
    <%
        GotoAuthSubLink.Visible = false;
        
        if (Session["token"] != null)
        {
            PrintCalendar();
        }
        else if (Request.QueryString["token"] != null)
        {
            String token = Request.QueryString["token"];
            Session["token"] = AuthSubUtil.exchangeForSessionToken(token, null).ToString();
            Response.Redirect(Request.Url.AbsolutePath, true);
        }
        else //no auth data, print link
        {
            GotoAuthSubLink.Text = "Login to your Google Account";
            GotoAuthSubLink.Visible = true;
            GotoAuthSubLink.NavigateUrl = AuthSubUtil.getRequestUrl(Request.Url.ToString(),
                "http://www.google.com/calendar/feeds/",false,true);
        }
        
     %>
    <asp:HyperLink ID="GotoAuthSubLink" runat="server"/>

    </div>
    </form>
</body>
</html>

总结

AuthSub 可让您的 Web 应用以安全且受控的方式访问存储在用户 Google 账号中的数据。使用 .NET 客户端库可以轻松将基于 ASP 的网站与 Google 服务集成。本文旨在帮助您入门,但我们建议您参考以下其他资源: