65.9K
CodeProject 正在变化。 阅读更多。
Home

通过 OAuth 在 .NET Framework 中进行用户授权

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (62投票s)

2015 年 2 月 14 日

Apache

14分钟阅读

viewsIcon

250346

downloadIcon

9423

通过 OAuth 为网站和程序授权用户。使用不同提供商的 API:CodeProject 论坛查看器、Dropbox 文件

OAuth

目录

引言

大型网站项目可以向第三方网站和应用程序提供对其成员资源的部分访问权限。这在大多数情况下是通过使用 **OAuth** 协议实现的。

**OAuth** 是一种开放的授权协议。该协议可以保密用户的凭据。通过访问令牌来处理用户的资源。

我知道这个想法并不新颖,但在某个平行宇宙中,我为 **.NET Framework** 项目创建了自己的 OAuth 授权库。

该库提供了实现 **OAuth** 客户端的机制,并且还包含流行网站的现成客户端。

主要目标是简单和极简。至少在我开始撰写本文时是这样。不幸的是(或幸运的是),在过程中,我发布了该库的两个新版本。使用该库仍然很简单,但源代码量已大大增加。但您不需要查看项目源代码。所以一切都很好。 :-)

主要功能是能够从用户配置文件中获取基本信息。这样,您就可以获取电子邮件地址、电话号码、用户姓名、网站链接和照片。这是大多数支持通过 OAuth 协议进行用户身份验证的网站首先需要的功能。

该库主要面向 Web,但也可以用于其他类型的项目。

源代码是开放的,使用 **C#** 编写,并根据 **Apache License Version 2.0** 获得许可。

该库需要 **.NET Framework 3.5** 或更高版本。
项目源代码和示例使用 **Visual Studio 2010 (SP1)** 为 **.NET Framework 4.0** 创建。
某些示例需要 **ASP.NET MVC 3.0**。

安装

本节的信息仅在您想在自己的项目中使用该库时才有意义。示例应无需额外操作即可运行。如果您不打算在自己的项目中使用该库,请跳过本节

开始之前,您需要向项目中添加对该库的引用。可以通过两种方式完成:

  1. 自动,通过 **程序包管理器控制台**(推荐)
  2. 手动添加对程序集的引用

使用程序包管理器控制台

要打开 **程序包管理器控制台**,请选择菜单:**工具** => **程序包管理器** => **程序包管理器控制台**。

Package Manager Console menu

在 **程序包源** 列表中,选择 **NuGet** 程序包源。

要安装 **Nemiro.OAuth**,请在 **程序包管理器控制台** 中运行以下命令:

PM> Install-Package Nemiro.OAuth

Package Manager Console

尽情享用!

手动添加引用

如果您没有 **程序包管理器** 或它不起作用,您可以下载 **Nemiro.OAuth** 并将程序集添加到您的项目中。

选择菜单 **项目**,然后单击 **添加引用...**。

Project => Add Reference...

选择 **浏览** 选项卡,找到并选择 **Nemiro.OAuth.dll**,然后单击 **确定**。

Add Reference to Nemiro.OAuth.dll

尽情享用!

关于 .NET Framework 版本的说明

在 **Windows Forms** 项目中,您可能需要更改 **.NET Framework** 的目标版本。

您必须使用完整版的 **.NET Framework**。

如果 **Visual Studio** 显示错误消息:“找不到类型或命名空间名称‘Nemiro’”,那么您应该更改 **.NET Framework** 的目标版本。

要更改目标版本,请选择菜单 **项目**,然后单击 **[您的应用程序名称] 属性...**。

在 **应用程序** 选项卡中,选择 **.NET Framework** 的目标版本,**不包括** **客户端配置文件**。

The traget version of the .NET Framework

**注意**:*如果您使用的是 **Visual Basic .NET** 和 **Visual Studio 2010**,那么更改 **.NET Framework** 的目标版本将在 **生成** 选项卡中的 **高级生成选项** 部分进行。*

使用库

首先,您需要在 **OAuth** 提供商的网站上创建一个应用程序。为您需要的每个提供商单独创建。

提供商将发布一个唯一的标识符和一个密钥给您的应用程序。初始化项目中提供商的客户端时应使用这些参数。

在提供商网站上正确配置应用程序是您将面临的最困难的部分。

对于 Web 项目,您需要指定回调地址。请注意,有些提供商不支持 **localhost** 和/或要求使用 **HTTPS** 协议。错误配置回调地址是出现问题最常见的原因。

您可以在 **Nemiro.OAuth** 库的文档中找到详细说明。

下表列出了 **OAuth** 提供商、它们的功能以及每个提供商的直接文档链接。

提供商 内部名称 localhost
支持
多个 URL
支持
HTTP
支持
Amazon amazon + + -
CodeProject codeproject - - -
Dropbox dropbox + + -
Facebook facebook + + +
Foursquare foursquare + + +
GitHub github - - +
Google google + + +
Instagram instagram + - +
LinkedIn linkedin + + +
Microsoft Live live - + +
Mail.Ru mail.ru - + +
Odnoklassniki odnoklassniki + + +
SoundCloud soundcloud + - +
SourceForge sourceforge + + +
Tumblr tumblr + - +
Twitter twitter + + +
VKontakte vk + + +
Yahoo! yahoo - - +
Yandex yandex + - +

**注意:** *表中的信息截至 2015 年 2 月。

**注意:** *内部名称不区分大小写。

**注意:** *如果提供商支持 **localhost**,则允许对 **localhost** 使用 **HTTP** 协议。
**HTTP** 支持字段仅适用于外部地址。

在获取应用程序的访问设置后,您可以在项目中注册特定提供商的客户端。

为了方便起见,请在代码中导入 `**Nemiro.OAuth**` 和 `**Nemiro.OAuth.Clients**` 命名空间。

using Nemiro.OAuth;
using Nemiro.OAuth.Clients;
Imports Nemiro.OAuth
Imports Nemiro.OAuth.Clients

**OAuth** 客户端类位于 `**Nemiro.OAuth.Clients**` 命名空间中。

您可以创建客户端实例,但这需要您手动执行所有操作。这并不方便。

**注意:** *作为初始化参数,您必须传递从提供商那里获得的 **应用程序 ID** 和 **密钥**。*

// creating an instance of the TwitterClient for twitter
var twitter = new TwitterClient
(
  "cXzSHLUy57C4gTBgMGRDuqQtr",
  "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P"
);
// redirect user to login page
// Response.Redirect(twitter.AuthorizationUrl);
// or for Windows Application open a login page in browser
// System.Diagnostics.Process.Start(twitter.AuthorizationUrl);
// wait an authorization code
// set authorization code
// twitter.AuthorizationCode = code;
// get user info
// var user = twitter.GetUserInfo();
' creating an instance of the TwitterClient for twitter
Dim twitter As New TwitterClient _
(
  "cXzSHLUy57C4gTBgMGRDuqQtr",
  "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P"
)
' redirect user to login page
' Response.Redirect(twitter.AuthorizationUrl)
' or for Windows Application open a login page in browser
' System.Diagnostics.Process.Start(twitter.AuthorizationUrl)
' wait an authorization code
' set authorization code
' twitter.AuthorizationCode = code
' get user info
' Dim user As UserInfo = twitter.GetUserInfo()

最好使用辅助类:`OAuthManager` 和 `OAuthWeb`。

要在您的应用程序中注册客户端,必须使用 `OAuthManager` 类的 `RegisterClient` 方法。
您可以创建客户端实例,也可以指定提供商的内部名称(列表显示在上面的表格中)。

// creating an instance of the DropboxClient for Dropbox
OAuthManager.RegisterClient
(
  new DropboxClient
  (
    "0le6wsyp3y085wy", 
    "48afwq9yth83y7u"
  )
);

// registration client for LinkedIn by provider name
OAuthManager.RegisterClient
(
  "LinkedIn",
  "75vufylz829iim",
  "VOf14z4T1jie4ezS"
);
// etc
' creating an instance of the DropboxClient for Dropbox
OAuthManager.RegisterClient _
(
  New DropboxClient _
  (
    "0le6wsyp3y085wy", 
    "48afwq9yth83y7u"
  )
)

' registration client for LinkedIn by provider name
OAuthManager.RegisterClient _
(
  "LinkedIn", 
  "75vufylz829iim", 
  "VOf14z4T1jie4ezS"
)
' etc

**注意:** *在应用程序中,您只能为每个提供商注册一个 **OAuth** 客户端。*

现在,借助 `OAuthWeb` 类,您可以获取用户在外站进行身份验证的地址。
为此,请使用 `GetAuthorizationUrl` 方法,该方法接受已注册提供商的内部名称,还可以接受用户授权后将被重定向到的地址。

如果您正在开发 **Windows** 应用程序且提供商允许,则指定回调地址不是必需的。但在 Web 项目中,这是始终必要的。

**注意**:*对于某些提供商,可以在应用程序设置(在提供商网站上)中指定回调地址。*

string url = OAuthWeb.GetAuthorizationUrl("dropbox");
Dim url As String = OAuthWeb.GetAuthorizationUrl("dropbox")

在用户返回到回调地址后,可以使用 `OAuthWeb` 类的 `VerifyAuthorization` 方法来检查授权结果。

`VerifyAuthorization` 方法可以接受回调页面的地址。对于 Web 项目,无需显式传递地址。默认情况下,该方法会处理当前地址(`Request.Url`)。

该方法返回一个 `AuthorizationResult` 类的实例,如果成功,其中将包含访问令牌和用户信息。

var result = OAuthWeb.VerifyAuthorization("http://the received address from an OAuth provider");

if (result.IsSuccessfully)
{
  // successfully
  var user = result.UserInfo;
  Console.Write(String.Format("User ID:  {0}", user.UserId));
  Console.Write(String.Format("Name:     {0}", user.DisplayName));
  Console.Write(String.Format("Email:    {0}", user.Email));
}
else
{
  // error
  Console.Write(result.ErrorInfo.Message);
}
Dim result = OAuthWeb.VerifyAuthorization("http://the received address from an OAuth provider")

If result.IsSuccessfully Then
  ' successfully
  Dim user = result.UserInfo
  Console.Write(String.Format("User ID:  {0}", user.UserId))
  Console.Write(String.Format("Name:     {0}", user.DisplayName))
  Console.Write(String.Format("Email:    {0}", user.Email))
Else
  ' error
  Console.Write(result.ErrorInfo.Message)
End If

这非常简单。

接下来将详细介绍在 **ASP.NET WebForms**、**MVC** 和 **Windows Forms** 项目中实现 OAuth 协议授权的过程。

OAuth 授权在 ASP.NET 中

在 **ASP.NET** 项目中通过 **OAuth** 协议实现授权只需几个简单的步骤!

  1. 将 `**Global.asax**` 文件添加到您的项目中(如果不存在)。打开 `**Global.asax**` 并将 OAuth 客户端注册代码添加到 `Application_Start` 处理程序中。例如,如果您想通过 **Yahoo!** 和 **Twitter** 实现授权,则需要注册客户端:`YahooClient` 和 `TwitterClient`。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    using Nemiro.OAuth;
    using Nemiro.OAuth.Clients;
    
    namespace Test.CSharp.AspWebForms
    {
      public class Global : System.Web.HttpApplication
      {
        protected void Application_Start(object sender, EventArgs e)
        {
          OAuthManager.RegisterClient
          (
            "yahoo",
            "dj0yJmk9Qm1vZ3p2TmtQUm4zJmQ9WVdrOU4wbGlkWGxJT" +
            "kc4bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD0xZQ--",
            "a55738627652db0acfe464de2d9be13963b0ba1f"
          );
          
          OAuthManager.RegisterClient
          (
            "twitter",
            "cXzSHLUy57C4gTBgMGRDuqQtr",
            "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P"
          );
        }
      }
    }
    Imports Nemiro.OAuth
    Imports Nemiro.OAuth.Clients
    
    Public Class Global_asax
      Inherits System.Web.HttpApplication
      
      Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        OAuthManager.RegisterClient _
        (
          "yahoo",
          "dj0yJmk9Qm1vZ3p2TmtQUm4zJmQ9WVdrOU4wbGlkWGxJT" &
          "kc4bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD0xZQ--",
          "a55738627652db0acfe464de2d9be13963b0ba1f"
        )
        
        OAuthManager.RegisterClient _
        (
          "twitter",
          "cXzSHLUy57C4gTBgMGRDuqQtr",
          "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P"
        )
      End Sub
    End Class

    **注意:** *请使用您从 **OAuth** 提供商处获取的应用程序的**应用程序 ID** 和**密钥**。本文档中提供的访问设置的可用性无法保证。*

    **注意:** *本文档中提供的 **Yahoo!** 的访问设置将无法正常工作,因为 **Yahoo!** 中的应用程序链接到特定的主机名。请使用您自己的访问设置。另外请注意,**Yahoo!** 不支持 **localhost**。*

    ASP.NET WebForms

  2. 在您网站的页面上,放置按钮以将用户重定向到外部站点进行身份验证。例如,您可以创建一个 `**UserControl**`,或者将按钮放在 `**MasterPage**` 上。

    **注意:** *建议使用服务器按钮,因为在获取授权地址时,服务器内存会创建唯一的会话。*

    所有按钮都可以有一个通用的单击事件处理程序。为了区分按钮,您可以添加 `data-provider` 属性,其中包含提供商名称。

    <asp:LinkButton ID="lnkFacebook" runat="server" data-provider="yahoo" 
    Text="Login with Yahoo!" onclick="RedirectToLogin_Click" /><br />
    <asp:LinkButton ID="lnkTwitter" runat="server" data-provider="twitter" 
    Text="Login with Twitter" onclick="RedirectToLogin_Click" />
    protected void RedirectToLogin_Click(object sender, EventArgs e)
    {
      // gets a provider name from the data-provider
      string provider = ((LinkButton)sender).Attributes["data-provider"];
      // build the return address
      string returnUrl = new Uri(Request.Url, "ExternalLoginResult.aspx").AbsoluteUri;
      // redirect user into external site for authorization
      OAuthWeb.RedirectToAuthorization(provider, returnUrl);
    }
    Protected Sub RedirectToLogin_Click(sender As Object, e As System.EventArgs)
      ' gets a provider name from the data-provider
      Dim provider As String = CType(sender, LinkButton).Attributes("data-provider")
      ' build the return address
      Dim returnUrl As String = New Uri(Request.Url, "ExternalLoginResult.aspx").AbsoluteUri
      ' redirect user into external site for authorization
      OAuthWeb.RedirectToAuthorization(provider, returnUrl)
    End Sub
  3. 创建一个新页面 - `**ExternalLoginResult.aspx**`。此页面将处理授权结果。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using Nemiro.OAuth;
    
    namespace Test.CSharp.AspWebForms
    {
    
      public partial class ExternalLoginResult : System.Web.UI.Page
      {
      
        protected void Page_Load(object sender, EventArgs e)
        {
          var result = OAuthWeb.VerifyAuthorization();
          
          Response.Write(String.Format("Provider: {0}<br />", result.ProviderName));
          
          if (result.IsSuccessfully)
          {
             // successfully
            var user = result.UserInfo;
            Response.Write(String.Format("User ID:  {0}<br />", user.UserId));
            Response.Write(String.Format("Name:     {0}<br />", user.DisplayName));
            Response.Write(String.Format("Email:    {0}", user.Email));
          }
          else
          {
            // error
            Response.Write(result.ErrorInfo.Message);
          }
        }
        
      }
      
    }
    Imports Nemiro.OAuth
    
    Public Class ExternalLoginResult
      Inherits System.Web.UI.Page
      
      Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim result As AuthorizationResult = OAuthWeb.VerifyAuthorization()
        
        Response.Write(String.Format("Provider: {0}<br />", result.ProviderName))
        
        If result.IsSuccessfully Then
          ' successfully
          Dim user As UserInfo = result.UserInfo
          Response.Write(String.Format("User ID:  {0}<br />", user.UserId))
          Response.Write(String.Format("Name:     {0}<br />", user.DisplayName))
          Response.Write(String.Format("Email:    {0}", user.Email))
        Else
          ' error
          Response.Write(result.ErrorInfo.Message)
        End If
      End Sub
      
    End Class

    尽情享用!

ASP.NET MVC

  1. 将 `ExternalLogin` 方法添加到 `HomeController` 控制器。此方法将接收提供商名称,并将用户重定向到外部站点进行身份验证。
    public ActionResult ExternalLogin(string provider)
    {
      // build the return address
      string returnUrl = Url.Action("ExternalLoginResult", "Home", null, null, Request.Url.Host);
      // redirect user into external site for authorization
      return Redirect(OAuthWeb.GetAuthorizationUrl(provider, returnUrl));
    }
    Public Function ExternalLogin(provider As String) As ActionResult
      ' build the return address
      Dim returnUrl As String = Url.Action("ExternalLoginResult", "Home", Nothing, Nothing, Request.Url.Host)
      ' redirect user into external site for authorization
      Return Redirect(OAuthWeb.GetAuthorizationUrl(provider, returnUrl))
    End Function
  2. 添加另一个名为 `ExternalLoginResult` 的方法,该方法将处理授权结果。
    public ActionResult ExternalLoginResult()
    {
      string output = "";
      var result = OAuthWeb.VerifyAuthorization();
      
      output += String.Format("Provider: {0}\r\n", result.ProviderName);
      
      if (result.IsSuccessfully)
      {
        // successfully
        var user = result.UserInfo;
        output += String.Format("User ID:  {0}\r\n", user.UserId);
        output += String.Format("Name:     {0}\r\n", user.DisplayName);
        output += String.Format("Email:    {0}", user.Email);
      }
      else
      {
        // error
        output += result.ErrorInfo.Message;
      }
      
      return new ContentResult
      { 
        Content = output, 
        ContentType = "text/plain" 
      };
    }
    Public Function ExternalLoginResult() As ActionResult
      Dim output As String = ""
      Dim result As AuthorizationResult = OAuthWeb.VerifyAuthorization()
      
      output &= String.Format("Provider: {0}", result.ProviderName)
      output &= vbCrLf
      
      If result.IsSuccessfully Then
        ' successfully
        Dim user As UserInfo = result.UserInfo
        output &= String.Format("User ID:  {0}", user.UserId)
        output &= vbCrLf
        output &= String.Format("Name:     {0}", user.DisplayName)
        output &= vbCrLf
        output &= String.Format("Email:    {0}", user.Email)
      Else
        ' error
        output &= result.ErrorInfo.Message
      End If
      
      Return New ContentResult() With _
      { 
        .Content = output, 
        .ContentType = "text/plain" 
      }
    End Function
  3. 现在,只需在页面上放置链接即可触发 `ExternalLogin` 操作。
    @{string url = Url.Action("ExternalLogin",new { provider="yahoo" });}
    <a href="#" onclick="window.location.href='@(url);return false;'">Login with Yahoo!</a>
    <br />
    @{url = Url.Action("ExternalLogin", new { provider="twitter" });}
    <a href="#" 
    onclick="window.location.href='@(url);return false;'">Login with Twitter</a>

    **注意:** *建议使用 **JavaScript** 链接,因为在获取授权地址时,服务器内存会创建唯一的会话。*

    尽情享用!

OAuth 授权在 Windows Forms 中

在 **Windows Forms** 项目中,可以通过 `WebBrowser` 控件实现通过 **OAuth** 协议进行的身份验证。

现成的表单

您可以使用现成的表单。为此,必须向项目中添加另一个程序集 - `**Nemiro.OAuth.LoginForms**`。

要安装该库,请在 **程序包管理器控制台** 中运行以下命令:

PM> Install-Package Nemiro.OAuth.LoginForms

或下载并向项目中添加对程序集的引用。

`**Nemiro.OAuth.LoginForms**` 库包含作为窗体的 OAuth 提供商类。
要开始使用这些类,请在代码中导入 `**Nemiro.OAuth.LoginForms**`。

using Nemiro.OAuth.LoginForms;
Imports Nemiro.OAuth.LoginForms

使用这些类非常简单。要获取访问令牌,必须完成三个步骤。

  1. 创建必要提供商的授权窗体的新实例。使用您从 **OAuth** 提供商那里获得的应用程序的**客户端 ID** 和**客户端密钥**。例如,Instagram 的登录表单。
    var login = new InstagramLogin
    (
      "9fcad1f7740b4b66ba9a0357eb9b7dda", 
      "3f04cbf48f194739a10d4911c93dcece", 
      "http://oauthproxy.nemiro.net/"
    );
    Dim login As New InstagramLogin _
    (
      "9fcad1f7740b4b66ba9a0357eb9b7dda", 
      "3f04cbf48f194739a10d4911c93dcece", 
      "http://oauthproxy.nemiro.net/"
    )

    **注意:** *对于 **Instagram**,需要指定回调地址。
    我为此创建了一个中间网关。此地址在 **Instagram** 网站上的应用程序设置中指定。
    您可以在自己的项目中也使用此地址,但不能保证它始终有效。*

  2. 以对话框模式显示窗体。
    login.ShowDialog();
    login.ShowDialog()
  3. 关闭登录表单后,您可以检查授权结果。
    if (login.IsSuccessfully)
    {
      MessageBox.Show
      (
        String.Format("Access token: {0}", login.AccessTokenValue), 
        "Successfully", 
        MessageBoxButtons.OK, 
        MessageBoxIcon.Information
      );
    }
    If login.IsSuccessfully Then
      MessageBox.Show _
      (
        String.Format("Access token: {0}", login.AccessTokenValue), 
        "Successfully", 
        MessageBoxButtons.OK, 
        MessageBoxIcon.Information
      )
    End If

    使用访问令牌处理 **API**。

自定义表单

如果需要,您可以创建自己的授权表单。

  1. 在主窗体的加载事件处理程序或应用程序初始化时,注册您需要的 **OAuth** 客户端。例如,如果您想通过 **Facebook** 和 **Yandex** 实现授权,则需要注册客户端:`FacebookClient` 和 `YandexClient`。
    private void Form1_Load(object sender, EventArgs e)
    {
      OAuthManager.RegisterClient
      (
        new FacebookClient
        (
          "1435890426686808",
          "c6057dfae399beee9e8dc46a4182e8fd"
        ) 
        { 
          Parameters = new NameValueCollection { { "display", "popup" } }
        }
      );
      
      OAuthManager.RegisterClient
      (
        new YandexClient
        (
          "0ee5f0bf2cd141a1b194a2b71b0332ce",
          "59d76f7c09b54ad38e6b15f792da7a9a"
        )
      );
    }
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
      OAuthManager.RegisterClient _
      (
        New FacebookClient _
        (
          "1435890426686808",
          "c6057dfae399beee9e8dc46a4182e8fd"
        ) _
        With
        {
          .Parameters = New NameValueCollection() From {{"display", "popup"}}
        }
      )
      
      OAuthManager.RegisterClient _
      (
        New YandexClient _
        (
          "0ee5f0bf2cd141a1b194a2b71b0332ce",
          "59d76f7c09b54ad38e6b15f792da7a9a"
        )
      )
    End Sub
  2. 在窗体上放置两个按钮,并添加一个通用的单击处理程序(`btn_Click`),该处理程序将打开登录窗口。在 `Tag` 属性中必须指定 **OAuth** 提供商的名称。
    private void btn_Click(object sender, EventArgs e)
    {
      var frm = new frmLogin(((Button)sender).Tag.ToString());
      frm.ShowDialog();
    }
    Private Sub btn_Click(sender As System.Object, e As System.EventArgs)
      Call New Form2(CType(sender, Button).Text).ShowDialog()
    End Sub
  3. 添加一个新窗体(命名为 `frmLogin`)并放置 `WebBrowser` 控件。
  4. 为 `frmLogin` 窗体创建一个构造函数,它接受一个 `string` 参数。初始化窗体时,将形成用户身份验证地址,该地址将设置为 `WebBrowser`。
    public frmLogin(string providerName)
    {
      InitializeComponent();
      webBrowser1.Navigate(OAuthWeb.GetAuthorizationUrl(providerName));
    }
    Public Sub New(providerName As String)
      InitializeComponent()
      WebBrowser1.Navigate(OAuthWeb.GetAuthorizationUrl(providerName))
    End Sub
  5. 在 `WebBrowser` 的 `DocumentCompleted` 事件处理程序中,放置代码来搜索和验证授权结果。
    private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
      // looking forward to the desired address
      if 
      (
        e.Url.Query.IndexOf("code=") != -1 || 
        e.Url.Fragment.IndexOf("code=") != -1 ||
        e.Url.Query.IndexOf("oauth_verifier=") != -1
      )
      {
        // verify
        var result = OAuthWeb.VerifyAuthorization(e.Url.ToString());
        
        if (result.IsSuccessfully)
        {
          // show user profile
          MessageBox.Show
          (
            String.Format
            (
              "User ID: {0}\r\nUsername: {1}\r\nDisplay Name: {2}\r\nE-Mail: {3}", 
              result.UserInfo.UserId,
              result.UserInfo.UserName,
              result.UserInfo.DisplayName ?? result.UserInfo.FullName,
              result.UserInfo.Email
            ), 
            "Successfully", 
            MessageBoxButtons.OK, 
            MessageBoxIcon.Information
          );
        }
        else
        {
          // error
          MessageBox.Show
          (
            result.ErrorInfo.Message, "Error",
            MessageBoxButtons.OK, MessageBoxIcon.Error
          );
        }
        
        this.Close();
      }
    }
    Private Sub WebBrowser1_DocumentCompleted _
    (
      sender As System.Object, 
      e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs
    ) Handles WebBrowser1.DocumentCompleted
      ' looking forward to the desired address
      If Not e.Url.Query.IndexOf("code=") = -1 OrElse _
        Not e.Url.Fragment.IndexOf("code=") = -1 OrElse _
        Not e.Url.Query.IndexOf("oauth_verifier=") = -1 Then
        ' verify
        Dim result = OAuthWeb.VerifyAuthorization(e.Url.ToString())
        If result.IsSuccessfully Then
          ' show user profile
          Dim displayName As String = result.UserInfo.DisplayName
          If String.IsNullOrEmpty(displayName) Then
            displayName = result.UserInfo.FullName
          End If
          MessageBox.Show _
          (
            String.Format _
            (
              "User ID: {0}{4}Username: {1}{4}Display Name: {2}{4}E-Mail: {3}",
              result.UserInfo.UserId,
              result.UserInfo.UserName,
              displayName, 
              result.UserInfo.Email, 
              vbNewLine
            ),
            "Successfully",
            MessageBoxButtons.OK,
            MessageBoxIcon.Information
          )
        Else
          ' error
          MessageBox.Show _
          (
            result.ErrorInfo.Message, "Error", 
            MessageBoxButtons.OK, MessageBoxIcon.Error
          )
        End If
        Me.Close()
      End If
    End Sub

    尽情享用!

使用 API

每个 **OAuth** 提供商都有其独特的 **API**。不幸的是,没有通用的 API 工作规则。但是,该库通过 **API** 接收用户信息,您可以使用这些方法来实现其他请求。

所有对服务器的请求都使用 `OAuthUtility` 类的 `ExecuteRequest` 方法执行。此方法返回一个 `RequestResult` 类的实例。

`RequestResult` 类能够处理各种格式的服务器响应。无论响应格式如何,结构化数据都将以集合形式呈现。这应该会简化与各种 **API** 的工作。

接下来将展示一些使用访问令牌与各种网站 **API** 交互的简单示例。

访问令牌

要使用 **API**,需要**访问令牌**,并且它是在用户授权时颁发的。

可以在 `AuthorizationResult` 或 `LoginForm` 类的 `AccessTokenValue` 属性中找到**访问令牌**。

var result = OAuthWeb.VerifyAuthorization();

if (result.IsSuccessfully)
{
  Console.WriteLine("Access token: {0}",  result.AccessTokenValue);
}
Dim result = OAuthWeb.VerifyAuthorization()

If result.IsSuccessfully Then
  Console.WriteLine("Access token: {0}",  result.AccessTokenValue)
End If

您可以将**访问令牌**存储在文件或数据库中。

CodeProject

**CodeProject** 有一个不错的 **API**。例如,我们可以创建一个程序来查看论坛。

这将是一个 **Windows Forms** 应用程序。

打开项目属性,然后转到**设置**部分。添加 `**AccessTokent**` 项。访问令牌将存储在此参数中。

Application settings

接下来,创建并显示授权表单。

// create login form
var login = new CodeProjectLogin
(
  "92mWWELc2DjcL-6tu7L1Py6yllleqSCt", 
  "YJXrk_Vzz4Ps02GqmaUY-aSLucxh4kfLq6oq0CtiukPfvbzb9yQG69NeDr2yiV9M", 
  "https://oauthproxy.nemiro.net/"
);

// set owner
login.Owner = this;

// show login form
login.ShowDialog();
' create login form
Dim login As New CodeProjectLogin _
(
  "92mWWELc2DjcL-6tu7L1Py6yllleqSCt", 
  "YJXrk_Vzz4Ps02GqmaUY-aSLucxh4kfLq6oq0CtiukPfvbzb9yQG69NeDr2yiV9M", 
  "https://oauthproxy.nemiro.net/"
)

' set owner
login.Owner = Me

' show login form
login.ShowDialog()

成功登录后,必须保存访问令牌。

// authorization is success
if (login.IsSuccessfully)
{
  // save the access token to the application settings
  Properties.Settings.Default.AccessToken = login.AccessTokenValue;
  Properties.Settings.Default.Save();
}
' authorization is success
If login.IsSuccessfully Then
  ' save the access token to the application settings
  My.Settings.AccessToken = login.AccessTokenValue
  My.Settings.Save()
End If

然后,使用访问令牌可以获取消息列表。为了不让程序冻结,最好使用异步请求。

int forumId = 1650; // General .NET Framework board

OAuthUtility.GetAsync
(
  String.Format("https://api.codeproject.com/v1/Forum/{0}/Threads", forumId),
  new HttpParameterCollection 
  { 
    { "page", 1 }
  },
  authorization: new HttpAuthorization(AuthorizationType.Bearer, Properties.Settings.Default.AccessToken),
  callback: UpdateList_Result
);
Dim forumId As Integer = 1650 ' General .NET Framework board

OAuthUtility.GetAsync _
(
  String.Format("https://api.codeproject.com/v1/Forum/{0}/Threads", forumId),
  New HttpParameterCollection From _
  { 
    New HttpUrlParameter("page", 1)
  },
  authorization:=New HttpAuthorization(AuthorizationType.Bearer, My.Settings.AccessToken),
  callback:=AddressOf UpdateList_Result
)

服务器响应将传递给 `UpdateList_Result` 方法。消息列表可以在 `DataGridView` 中显示。

`DataGridView` 有三列。每列包含数据绑定参数。

private void UpdateList_Result(RequestResult result)
{
  if (this.InvokeRequired)
  {
    this.Invoke(new Action<RequestResult>(UpdateList_Result), result);
    return;
  }

  dataGridView1.DataSource = result["items"].Select
  (
    item => new 
    {
      title = item["title"].ToString(),
      author = item["authors"].First()["name"].ToString(),
      createdDate = Convert.ToDateTime(item["createdDate"])
    }
  ).ToArray();
}
Private Sub UpdateList_Result(result As RequestResult)
  If Me.InvokeRequired Then
    Me.Invoke(New Action(Of RequestResult)(AddressOf UpdateList_Result), result)
    Return
  End If

  DataGridView1.DataSource = result("items").Select _
  (
    Function(item)
      Return New With _
      {
        .title = item("title").ToString(),
        .author = item("authors").First()("name").ToString(),
        .createdDate = Convert.ToDateTime(item("createdDate"))
      }
    End Function
  ).ToArray()
End Sub

当然,本文档中的描述并不完整。不过,使用 **API** 相当简单,您可以创建下图所示的应用程序。

CodeProject forum viewer

您可以在源代码文件中找到该项目。项目名为 `**CodeProjectForumViewer**`。不幸的是,它仅适用于 **C#**。

Dropbox

创建一个 **Dropbox** 的文件管理器也非常简单。

基本原理对所有 **Windows Forms** 项目都相同。

  1. 创建并显示授权表单。
    var login = new DropboxLogin("4mlpoeq657vuif8", "1whj6c5mxtkns7m");
    login.Owner = this;
    login.ShowDialog();
    Dim login As New DropboxLogin("4mlpoeq657vuif8", "1whj6c5mxtkns7m")
    login.Owner = Me
    login.ShowDialog()
  2. 将访问令牌保存在应用程序设置中。
    if (login.IsSuccessfully)
    {
      Properties.Settings.Default.AccessToken = login.AccessTokenValue;
      Properties.Settings.Default.Save();
    }
    If login.IsSuccessfully Then
      My.Settings.AccessToken = login.AccessTokenValue
      My.Settings.Save()
    End If

    拥有访问令牌后,您可以获取文件列表……

    OAuthUtility.GetAsync
    (
      "https://api.dropbox.com/1/metadata/auto/",
      new HttpParameterCollection
      {
        { "path", "/" },
        { "access_token", Properties.Settings.Default.AccessToken }
      },
      callback: GetFiles_Result
    );
    OAuthUtility.GetAsync _
    (
      "https://api.dropbox.com/1/metadata/auto/",
      new HttpParameterCollection From _
      {
        New HttpUrlParameter("path", "/"),
        New HttpUrlParameter("access_token", My.Settings.AccessToken)
      },
      callback:=AddressOf GetFiles_Result
    );

    ……并在 `ListBox` 中显示。

    private void GetFiles_Result(RequestResult result)
    {
      if (this.InvokeRequired)
      {
        this.Invoke(new Action<RequestResult>(GetFiles_Result), result);
        return;
      }
    
      if (result.StatusCode == 200)
      {
        // show a list of files 
        listBox1.Items.Clear();
        listBox1.DisplayMember = "path";
        foreach (UniValue file in result["contents"])
        {
          listBox1.Items.Add(file);
        }
      }
      else
      {
        // show an error message
        if (result["error"].HasValue)
        {
          MessageBox.Show
          (
            result["error"].ToString(), 
            "Error", 
            MessageBoxButtons.OK, 
            MessageBoxIcon.Error
          );
        }
        else
        {
          MessageBox.Show(result.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
      }
    }
    Private Sub GetFiles_Result(result As RequestResult)
      If Me.InvokeRequired Then
        Me.Invoke(New Action(Of RequestResult)(AddressOf GetFiles_Result), result)
        Return
      End If
    
      If result.StatusCode = 200 Then
        ' show a list of files 
        ListBox1.Items.Clear()
        ListBox1.DisplayMember = "path"
        For Each file As UniValue In result("contents")
          ListBox1.Items.Add(file)
        Next
      Else
        ' show an error message
        If result["error"].HasValue Then
          MessageBox.Show _
          (
            result("error").ToString(), 
            "Error", 
            MessageBoxButtons.OK, 
            MessageBoxIcon.Error
          )
        Else
          MessageBox.Show(result.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
      End If
    End Sub

    您可以通过 `**PUT**` 方法直接从 `Stream` 上传文件到 **Dropbox**。

    if (openFileDialog1.ShowDialog() != System.Windows.Forms.DialogResult.OK) { return; }
    
    OAuthUtility.PutAsync
    (
      "https://api-content.dropbox.com/1/files_put/auto/",
      new HttpParameterCollection
      {
        {"access_token", Properties.Settings.Default.AccessToken},
        {"path", Path.Combine("/", Path.GetFileName(openFileDialog1.FileName)).Replace("\\", "/")},
        {"overwrite", "true"},
        {"autorename","true"},
        {openFileDialog1.OpenFile()}
      },
      callback: Upload_Result
    );
    If Not OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then Return
    
    OAuthUtility.PutAsync _
    (
      "https://api-content.dropbox.com/1/files_put/auto/",
      New HttpParameterCollection From _
      {
        New HttpUrlParameter("access_token", My.Settings.AccessToken),
        New HttpUrlParameter _
        (
          "path", Path.Combine("/", Path.GetFileName(OpenFileDialog1.FileName)).Replace("\\", "/")
        ),
        New HttpUrlParameter("overwrite", "true"),
        New HttpUrlParameter("autorename","true"),
        New HttpRequestBody(OpenFileDialog1.OpenFile())
      },
      callback:=AddressOf Upload_Result
    ) 

要查看文件管理器开发的完整流程,您可以观看下一个视频。

Show video

视频中展示的项目源代码可以在 `**MyDropBox**` 文件夹中找到。

`**DropboxExample**` 文件夹中展示了一个更复杂的示例。

不幸的是,它仅适用于 **C#**。

Dropbox application

Twitter

**Twitter** 使用过时的 **OAuth** 协议版本。

对于每个请求,除了**访问令牌**之外,还需要**令牌密钥**和**签名**请求。这不像人们期望的那样简单。

您可以使用 `OAuthAuthorization` 类。该类将自动为每个 **API** 请求签名。

您可以创建一个辅助方法来返回请求的授权头。

private string _ConsumerKey = "cXzSHLUy57C4gTBgMGRDuqQtr";
private string _ConsumerSecret = "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P";

private OAuthAuthorization GetAuth()
{
  var auth = new OAuthAuthorization();
  auth.ConsumerKey = _ConsumerKey; 
  auth.ConsumerSecret = _ConsumerSecret;
  auth.SignatureMethod = SignatureMethods.HMACSHA1;
  auth.Token = Properties.Settings.Default.AccessToken;
  auth.TokenSecret = Properties.Settings.Default.TokenSecret;
  return auth;
}
Private _ConsumerKey As String = "cXzSHLUy57C4gTBgMGRDuqQtr"
Private _ConsumerSecret As String = "3SSldiSb5H4XeEMOIIF4osPWxOy19jrveDcPHaWtHDQqgDYP9P"

Private Function GetAuth() As OAuthAuthorization
  Dim auth As New OAuthAuthorization()
  auth.ConsumerKey = _ConsumerKey
  auth.ConsumerSecret = _ConsumerSecret
  auth.SignatureMethod = SignatureMethods.HMACSHA1
  auth.Token = My.Settings.AccessToken
  auth.TokenSecret = My.Settings.TokenSecret
  Return auth
End Function

现在您可以使用此方法向 **Twitter** 发送请求。例如,您可以获取推文列表。

OAuthUtility.GetAsync
(
  "https://api.twitter.com/1.1/statuses/user_timeline.json",
  authorization: GetAuth(),
  callback: GetTweets_Result
);
OAuthUtility.GetAsync _
(
  "https://api.twitter.com/1.1/statuses/user_timeline.json",
  authorization:=GetAuth(),
  callback:=AddressOf GetTweets_Result
)

如果响应成功,结果将是一个推文数组。

private void GetTweets_Result(RequestResult result)
  if (this.InvokeRequired)
  {
    this.Invoke(new Action<RequestResult>(GetTweets_Result), result);
    return;
  }

  if (result.IsSuccessfully)
  {
    for(int i = 0; i <= result.Count - 1; i++)
    {
      UniValue item = result[i];
      string username = item["user"]["screen_name"].ToString();
      DateTime dateCreated = DateTime.ParseExact 
      (
        item["created_at"].ToString(), 
        "ddd MMM dd HH:mm:ss zzzz yyyy", 
        CultureInfo.InvariantCulture
      );
      Console.WriteLine("@{0} / {1}", username, dateCreated);
      Console.WriteLine(item["text"]);
      Console.WriteLine("------------------------------------------------------");
    }
  }
  else
  {
    Console.WriteLine(result);
  }
}
Private Sub GetTweets_Result(result As RequestResult)
  If Me.InvokeRequired Then
    Me.Invoke(New Action(Of RequestResult)(AddressOf GetTweets_Result), result)
    Return
  End If

  If result.IsSuccessfully Then
    For i As Integer = 0 To result.Count - 1
      Dim item As UniValue = result(i)
      Dim username As String = item("user")("screen_name")
      Dim dateFormat As String = "ddd MMM dd HH:mm:ss zzzz yyyy"
      Dim dateCreated As Date 
      dateCreated = Date.ParseExact(item("created_at"), dateFormat, CultureInfo.InvariantCulture)
      Console.WriteLine("@{0} / {1}", username, dateCreated)
      Console.WriteLine(item("text"))
      Console.WriteLine("------------------------------------------------------")
    Next
  Else
    Console.WriteLine(result)
  End If
End Sub

发布新推文时,您可能会遇到编码问题。为避免问题,您可以将内容类型指定为 **multipart/form-data**。

var parameters = new HttpParameterCollection();

parameters.AddFormParameter
(
  "status", 
  "Hello, world! Привет, человеки! Hallo Welt! " +
  "Hola mundo! Hola, món! Bonjour le monde! Ciao, mondo!" + 
  "Բարև, աշխարհ! ハロー・ワールド! 你好,世界"
);

OAuthUtility.PostAsync 
(
  "https://api.twitter.com/1.1/statuses/update.json",
  parameters: parameters,
  authorization: GetAuth(),
  contentType: "multipart/form-data"
);
Dim parameters As New HttpParameterCollection()

parameters.AddFormParameter _
(
  "status", 
  "Hello, world! Привет, человеки! Hallo Welt! " & 
  "Hola mundo! Hola, món! Bonjour le monde! Ciao, mondo!" & 
  "Բարև, աշխարհ! ハロー・ワールド! 你好,世界"
)

OAuthUtility.PostAsync _
(
  "https://api.twitter.com/1.1/statuses/update.json",
  parameters:=parameters,
  authorization:=GetAuth(),
  contentType:="multipart/form-data"
)

项目源代码可以在 `*TwitterExample.VB*` 文件夹中找到。不幸的是,仅适用于 **Visual Basic .NET**。

Twitter application

还有更多……

您可以在 **demo site** 的源代码(位于 `*Test.OAuthWeb*` 文件夹中)中找到更多与其他 **API** 交互的简单示例。

创建新的 OAuth 客户端

如果您需要创建自定义客户端,可以使用基类:**OAuth v1.0** 使用 `OAuthClient`,**OAuth v2.0** 使用 `OAuth2Client`。

建议使用 **OAuth v2.0**,因为它更简单(如果提供商支持此协议版本)。

创建一个新类并继承自基类。

public class MyClient : OAuth2Client
{
}
Public Class MyClient
  Inherits OAuth2Client

End Class

重写 `ProviderName` 属性并为您的客户端指定唯一的名称。

public override string ProviderName
{
  get
  {
    return "MyClient";
  }
}
Public Overrides ReadOnly Property ProviderName As String
  Get
    Return "MyClient"
  End Get
End Property

在类构造函数中,指定授权的基地址和获取访问令牌的地址。

public MyClient(string clientId, string clientSecret) : base
(
  "https://example.org/oauth",
  "https://example.org/oauth/access_token", 
  clientId,
  clientSecret
) { }
Public Sub New(clientId As String, clientSecret As String)
  MyBase.New _
  (
    "https://example.org/oauth",
    "https://example.org/oauth/access_token", 
    clientId, clientSecret
  )
End Sub

最难的部分是重写 `GetUserInfo` 方法。

您需要阅读提供商的文档,了解如何获取用户信息。

借助 `ApiDataMapping` 类,您可以创建 `UserInfo` 类的新实例,并用从提供商 **API** 获取的数据填充它。

例如,**CodeProject** 以 **JSON** 格式返回用户信息。

{
  "id": 1,
  "userName": "sample string 2",
  "displayName": "sample string 3",
  "avatar": "sample string 4",
  "email": "sample string 5",
}

您可以指定从获取的参数中哪些需要设置到 `UserInfo` 类的属性中。

例如:将 `id` 设置到 `UserId` 属性,`displayName` 设置到 `DisplayName`,`avatar` 设置到 `Userpic`,依此类推。

var map = new ApiDataMapping();
map.Add("id", "UserId", typeof(string));
map.Add("userName", "UserName");
map.Add("displayName", "DisplayName");
map.Add("email", "Email");
map.Add("avatar", "Userpic");
Dim map As New ApiDataMapping()
map.Add("id", "UserId", GetType(String))
map.Add("userName", "UserName")
map.Add("displayName", "DisplayName")
map.Add("email", "Email")
map.Add("avatar", "Userpic")

如果需要,您可以创建自定义数据处理程序。

map.Add
(
  "displayName", "DisplayName",
  delegate(UniValue value)
  {
    return value.ToString().ToUpper();
  }
);
map.Add _
(
  "displayName", "DisplayName",
  Function(value As UniValue)
    Return value.ToString().ToUpper()
  End Function
)

如果无法获取用户信息,`GetUserInfo` 方法可能会返回一个空的 `UserInfo` 类实例。

return new UserInfo(UniValue.Empty, null);
Return New UserInfo(UniValue.Empty, Nothing)

客户端的使用方式与其他客户端完全相同。

结语

使用外部资源的授权是推广网站和提高用户可用性的重要组成部分。

您可以显著提高用户参与度和忠诚度,并增加用户数量。

使用 **API**,您可以显著扩展应用程序的功能。

希望 `**Nemiro.OAuth**` 库对您有所帮助,并能简化与各种项目的集成。

项目源代码是开放的,您可以根据自己的意愿使用它。如果您有 GitHub 帐户,可以fork 项目仓库

历史

  • 2015 年 2 月 10 日:第一个版本
  • 2016 年 7 月 27 日:更新了源代码和二进制文件
  • 2016 年 8 月 8 日:更新了源代码和二进制文件
© . All rights reserved.