Security

使用 Windows 运行时和 OAuth 访问联机服务

Tim Kulp

下载代码示例

回顾从前的 Web 1.0 时代,网站只是供人阅读的内容的存储库,仅此而已。 当 Web 2.0 迅速应用于开发领域时,网站变成了联机服务,并配备了各种 API,供开发人员用来组合和匹配组件、数据和功能。 现在,开发人员可以通过混合 Web 应用程序来访问丰富的内容库,而避免了在其服务器机房中存储数据的开销。

借助于 Windows 运行时 (WinRT),您可以将混合 Web 应用程序的强大功能融入到您的下一个 Windows 应用商店应用程序中。 不论您是使用 XmlHttpRequest (XHR) 管理数据,还是使用 WebAuthenticationBroker 类向远程服务进行身份验证,Windows JavaScript 库 (WinJS) 和 Windows 运行时都使您能够轻松地将联机服务与应用程序相混合。

Contoso Photo Finish

在本文中,我打算构建一个名为 Contoso Photo Finish 的混合 Web 应用程序。 通过该应用程序,跑步者可以跟踪其里程并发布其跑步的图片。 许多跑步者喜欢在社交网络上分享其跑步的距离和位置等此类信息。 通过 Contoso Photo Finish,用户可以告诉朋友关于其跑步的信息并在 Facebook 上发布评论和图片。 此应用程序将连接到两个不同的服务:

  • 连接到 Windows SkyDrive 以检索关于跑步的图片
  • 连接到 Facebook 向朋友发布图片供其查看

Contoso Photo Finish 将结合这两项服务来为用户提供连接体验。 本文假定您已打开 Visual Studio 2012 并且 JavaScript | Windows 应用商店 | 空白应用程序模板已准备就绪,您可以开始编写代码了。

混合 Web 应用程序面临的障碍: 授权和身份验证

如果应用程序(Web 或 Windows 应用商店)要向 Facebook 发布内容,第一个要克服的障碍是身份验证。 Facebook 需要知道是谁正在连接到它。 当用户尝试登录应用程序时,他们会声明一个身份(通常以用户名形式)和一个凭据(如密码、安全令牌、生物特征设备等),以证明自己对所请求的身份应具有访问权限。 身份验证是通过凭据验证用户身份的过程。

在对用户进行身份验证后,混合 Web 应用程序将面临另一个挑战: 确定用户可以在系统中执行什么操作。 授权是根据某种属性或安全角色成员资格,允许或拒绝某个身份尝试执行操作的过程。 例如,在 Twitter 中,我的身份就无权删除所有用户的所有 tweet。 我无权执行该操作,因为我不是有权执行此操作的安全角色的成员。 身份验证和授权 (A&A) 两者共同提出了这样一个问题: 用户、用户身份以及用户可以执行什么操作?

混合 Web 应用程序使这些问题变得更麻烦,因为构建混合 Web 应用程序的开发人员对于存储身份、凭据和安全角色的位置(也称为凭据存储)不具备访问权限。 因此,如果我无法验证用户的身份以及用户可以执行什么操作,我怎么才能为诸如 Facebook 的应用程序构建混合 Web 应用程序呢? OAuth 应运而生!

OAuth: 访问资源,而非应用程序

OAuth 可以解决混合 Web 应用程序面临的 A&A 挑战。 不妨想像一下,应用程序 X 要访问联机服务 Y 中的内容。 此时用户不是针对应用程序 X 进行身份验证,而是针对联机服务 Y 进行身份验证,因为用户的凭据存储在联机服务 Y 的凭据存储中。 然后,用户允许应用程序 X 在有限的时间内访问联机服务 Y 中的指定内容。 访问联机服务 Y 资源的权限将作为访问令牌(有时就称为“令牌”)返回给应用程序 X。

在传统的 Web A&A 模型中,两个参与者共同确定用户的访问权限: 应用程序和用户(或服务器和客户端)。 在 OAuth 中,引入了第三个参与者: 资源服务器。 资源服务器是将资源(如照片)存储在客户端需要访问的服务器上的第三方。 在 Contoso Photo Finish 中,Facebook 是资源服务器。 Contoso Photo Finish 要访问的资源是用户的状态,以便发布消息。

OAuth 客户端及其身份验证过程

OAuth 中有两种类型的客户端,要使用哪种类型由客户端的信任级别决定。 机密客户端可以确保其凭据安全,这是高度信任的环境所应该提供的。 提供以下服务的服务器端 Web 应用程序就是机密客户端的一个示例:其中,客户端密钥能够在可控制的安全环境中得到维护。 机密客户端使用授权代码过程来获取安全令牌,其方法是向资源服务器提供客户端密钥以作为对客户端进行身份验证的手段。

公共客户端无法确保其凭据的安全性,因为它们在不友好的环境中运行。 用户代理应用程序(也即 JavaScript Web 应用程序)或本机应用程序(如 Windows 应用商店应用程序)就是公共客户端的示例。 公共客户端使用“隐式授予”过程来获取安全令牌,因为在开发人员无法控制的恶意环境中,无法以安全方式存储客户端密钥。

Windows 应用商店应用程序可以配置为对 OAuth 使用隐式授予或授权代码过程(您可以通过 bit.ly/yQjyQZ 阅读有关隐式授予和授权代码过程的详细信息)。 只要应用程序不受开发人员控制,就使用隐式授予,这是安全性的最佳实践。

图 1 中,我考察了隐式授予过程,其中客户端通过尝试向资源服务器确定用户身份来启动会话。

Windows Store App Implicit Grant Conversation
图 1 Windows 应用商店应用程序隐式授予会话

图 1 中所示的步骤解释如下:

  1. Windows 应用商店应用程序需要执行某种功能,而该功能要求对 Facebook API 具有访问权限。
  2. 用户通过一个 URI 连接到资源服务器,而该 URI 中包括有关试图访问 Facebook API 的 Windows 应用商店应用程序的信息。 这通常采用应用程序 ID 或客户端 ID 代码的形式。 用户提供用户名和密码以登录到 Facebook。
  3. 假设登录成功,此时 Facebook 向 Windows 应用商店应用程序提供一个访问令牌。
  4. Windows 应用商店应用程序现在可以使用由 Facebook 提供的访问令牌来获取用户的源、发布图片等等,从而向用户提供 Facebook API 中的数据。

通过新的 Windows.Security.Authentication.Web 命名空间,让这一会话发生将变得极其简单。

WebAuthenticationBroker

在 Windows 应用商店应用程序中,WebAuthenticationBroker 类 (bit.ly/RW8czT) 是这样一个组件:它将与资源服务器通信、提供登录控制并响应成功登录,所有这些都不需要 Windows 应用商店应用程序知道有关用户凭据的任何信息。 示例应用程序 Contoso Photo Finish 需要将图片发布到 Facebook。 这要求用户针对 Facebook 进行身份验证并收到一个访问令牌。

向名为 input.html 的 Contoso Photo Finish 项目添加一个新的页面控件。 默认情况下,Visual Studio 提供了许多标记。 将主要内容部分中的“<p>Content goes here.</p>”替换为以下按钮:

<input type="button" id="btnAddRun" value="Add Run" />

这是用户将单击以便向 Photo Finish 添加跑步的按钮。 现在打开 input.js 并添加以下函数:

function btnAddRun_Click(e) {
  var facebookOauthUrl = "https://www.facebook.com/dialog/oauth";
  var facebookClientId = "[YOUR CLIENT ID]";
  var redirectUrl = "https://www.facebook.com/connect/login_success.html";
  var requestUri = Windows.Foundation.Uri(facebookOauthUrl +
    "?client_id=" + facebookClientId +
    "&redirect_uri=" + encodeURIComponent(redirectUrl) +
    "&response_type=" +
    "token&scope=read_stream,publish_actions&display=popup");
  var callbackUri = Windows.Foundation.Uri(redirectUrl);
  // Web authentication broker will go here
}

此代码会建立要在身份验证请求中使用的变量。 第一个变量是 Facebook OAuth 服务的 URL。 客户端 ID 是一个应用程序标识符,Facebook 使用此标识符将 Contoso Photo Finish 标识为 Facebook API 将与其交互的应用程序。 此类标识符通常是在应用程序向资源服务器注册时分配给该应用程序的。 某些资源服务器将这些标识符称为客户端 ID、应用程序 ID 或仅仅称为 ID。 在资源服务器的 API 文档中可以找到要使用哪个 ID。

redirectUrl 参数确定当用户进行身份验证并批准应用程序访问指定的资源后,应用程序将前往何处。 在这种情况下,redirectUrl 设置为可用于所有 Facebook API 应用程序的 Facebook 标准位置。 一些服务要求在注册 Windows 应用商店应用程序时,应向资源服务器标识此应用程序的 URI。 可以使用 Web 身份验证代理程序的 getCurrentApplicationCallbackUri 方法找到应用程序的 URI。 这将返回应用程序的本地上下文 URI(以“ms-app://”开头)。 某些资源服务器不支持将 ms-app:// 作为 redirectUrl 的有效协议,在这种情况下,您应检查默认的重定向地址,如 Facebook 提供的此类重定向地址。

接下来,定义 callbackUri。 这是向 Web 身份验证代理程序通知何时完成身份验证并将控制权返还给 Windows 应用商店应用程序的地址。 代理程序从不实际转至此 URL;它只是监视资源服务器是否调用此页面,然后返回 callbackUri 以及附加的任何查询字符串或哈希参数。 在这种情况下,哈希参数“access_token”将向 Contoso Photo Finish 提供与此 API 交互所需的令牌。

WebAuthenticationBroker 类使用 authenticateAsync 方法连接到资源服务器,并针对资源服务器完成身份验证过程。 当调用 authenticateAsync 时,应用程序将打开一个弹出式窗口,其中显示资源服务器的登录屏幕,如图 2 所示。 一旦完成了身份验证或遇到了 callbackUri,弹出窗口将关闭。

Resource Server’s Login Pop-up from authenticateAsync
图 2 调用 authenticateAsync 时显示的资源服务器的登录弹出窗口

使用此弹出窗口的一个关键优势在于:对于资源管理器,Windows 应用商店应用程序从不处理用户的凭据,也不需要知道用户的凭据。 应用程序只需知道资源服务器返回的访问令牌即可。 这种分隔措施使资源服务器的凭据与应用程序相分离,这可避免通过应用程序存储凭据所带来的安全性风险。 除了安全性优势之外,开发人员还不必对任何内容编码即可获得此接口;此接口内置于 authenticateAsync 方法中。 当开发人员调用此方法时,此方法将立即提供此接口。

现在,让我们回过头来说一说代码。 用下列代码替换“Web authentication broker will go here”注释:

Windows.Security.Authentication.Web.WebAuthenticationBroker.
authenticateAsync(Windows.Security.Authentication.Web.
WebAuthenticationOptions.
none, requestUri, callbackUri)
.done(
  function (result) {
    // Check the response status here                 
  },
  function (ex) {
    Log(ex);
  }
);

authenticateAsync 方法采用三个参数(第三个是可选参数):

  1. WebAuthenticationOptions: 此参数用于向 Web 身份验证代理程序提供相关的说明,指示如何呈现身份验证对话框或在响应中返回什么数据。 在前面的示例中,应用程序使用“none”来说明一种常见的实施,也即使用默认设置,不向代理程序传递任何选项。
  2. requestUri: 这是资源服务器的登录入口点。 在此情况下,Contoso Photo Finish 连接到 Facebook 的 OAuth 服务。 RequestUri 必须使用 HTTPS 协议通过安全连接相连。
  3. callbackUri: 这是一个页面,当导航到此页面时,系统将控制权返还给 Windows 身份验证代理程序,如前面所述。 此参数是可选的,但如果资源服务器无法(或不想)重定向到 ms-app://,则此参数指示应用程序将以何种方式避开资源服务器的控制。 例如,在前面的代码中,当成功登录后导航到 https://www.facebook.com/connect/login\_success.html 时,Web 身份验证代理程序将通过关闭身份验证对话框并处理承诺的成功,从资源服务器手中接管对应用程序的控制权。 CallbackUri 不必位于紧接的下一页中;它可能位于向导之后,或位于资源服务器网站上必然出现的某个其他过程之后。 此 URI 通常与 redirectUrl 相同,但它提供了灵活性,可根据需要延长身份验证过程。

如果 Web 身份验证代理程序连接到资源服务器,则承诺获得成功。 检测身份验证过程的结果是通过 WebAuthenticationResult 对象的 ResponseStatus 属性来实现的。 在前面的代码中,结果参数是 WebAuthenticationResult 对象以及三个属性: 响应数据(来自资源服务器的数据)、ResponseErrorDetail(如果某项内容出错,则错误详情是什么?)以及 ResponseStatus(身份验证的状态是什么?)。 将“Check the response status here”注释替换为图 3 中所示的代码。

图 3 处理身份验证过程的结果

switch (result.responseStatus) {
  case Windows.Security.Authentication.Web.WebAuthenticationStatus.success:
    var fragment = Windows.Foundation.Uri(result.responseData).fragment;
    if (fragment.indexOf("#access_token=") != -1) {
      var token = fragment.substring(
        new String("#access_token=").length,
        fragment.indexOf("&expires_in="));
      // Add API calls here
    }
    break;
  case Windows.Security.Authentication.Web.WebAuthenticationStatus.userCancel:
    Log(window.toStaticHTML(result.responseData));
    Display("User cancelled the authentication to Facebook.");
    break;
  case Windows.Security.Authentication.Web.WebAuthenticationStatus.errorHttp:
    Log(window.toStaticHTML(result.responseData));
    Display("An error occurred while communicating with Facebook.");
    break;
}

图 3 所示的代码中,每种状态都进行了检查,Log 方法记录了来自资源服务器的信息,而 Display 方法告知用户发生了什么情况。 对于错误消息,请记得显示用户友好的消息以改进使用效果,并减少从系统生成的错误消息中意外泄漏敏感信息的可能性。 如果身份验证获得成功,系统将解析从 Facebook 返回的 URI 片段,并将其存储在令牌变量中以用于 API 调用(有关实施详情,请参阅“通过 XHR 获取和发布信息”部分。

当 btnAddRun_Click 函数完成时,将其连接到 WinJS.UI.Pages.define { ready } 函数中的 btnAddRun 对象。

var btnAddRun = document.getElementById("btnAddRun");
if (null != btnAddRun)
  btnAddRun.addEventListener("click", btnAddRun_Click, false);

此时,应用程序具有一个访问令牌,其中显示用户已通过资源服务器的身份验证。 在最后一部分中,应用程序将执行 API 命令以发送数据,但首先,应用程序需要将一些内容发送到 Facebook。

绘制图画

Windows 8 提供了各种允许应用程序彼此之间进行交流的合约,如搜索、共享和文件选取器。 通过这些合约,只需几行代码,就可以将任何应用程序转换为混合 Web 应用程序。 Contoso Photo Finish 打算利用文件选取器合约的强大功能,以找出用户跑步的图像。

我喜欢 Windows Phone 的众多原因之一是它与 SkyDrive 集成。 我可以立即将我的照片上载到云中的存储中,这样,下一次我摔坏手机时(经常发生这种情况),就可以在线获得我的图片。 Windows 8 的 SkyDrive 应用程序向文件选取器提供数据,这使得从我的 SkyDrive 帐户选择文件与从图片库中选择文件一样简单。 Contoso Photo Finish 混合 Web 应用程序的下一个部分将使用通过文件选取器从 SkyDrive 应用程序获得的数据。 为此,input.html 也需要一些 . . 输入内容。

使用图 4 中所示的代码替换现有的 btnAddRun 按钮。 此代码包括供用户为 Contoso Photo Finish 提供内容的输入字段。 btnSelectPhoto 按钮将使用文件选取器来选择系统上要使用的文件。 向 input.js 添加一个新函数,此函数将是 btnSelectPhoto 的单击处理程序:

function btnSelectPhoto_Click(e) {
  var imgSelectedPhoto = document.getElementById("imgSelectedPhoto");
  var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
  filePicker.fileTypeFilter.replaceAll([".jpg", ".jpeg", ".png"]);
  filePicker.suggestedStartLocation =
    Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
  filePicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;
  // Pick file here
}

图 4 为应用程序提供内容的输入字段

<p>
  <label>Distance</label>
  <input type="number" min="0" max="15" id="txtDistance"
    required /> miles
</p>
<p>
  <label>Comment</label>
  <input type="text" min="0" max="15" id="txtComment" />
</p>
<p>
  <label>Photo</label>
  <input id="btnSelectPhoto" value="Select Photo" type="button" />
  <img src="" id="imgSelectedPhoto" alt="Selected Photo" />
</p>
<p>
  <input type="button" id="btnAddRun" value="Add Run" />
</p>

此函数首先是设置 imgSelectedPhoto 变量,此变量将用于向用户显示所选照片。 接下来,该代码创建一个文件选取器对象。 通过此文件选取器对象,Contoso Photo Finish 可以在系统上或在参与文件选取器合约的其他应用程序中,选择要在此应用程序中打开并进行交互的文件或文件夹(此处只是文件)。 通过使用此文件类型筛选器,代码可限制文件选取器可以访问哪些文件扩展名。 应用程序只能将图像加载到 Facebook(按设计规定),因此,限制文件选取器只能处理指定的图像文件类型,可以避免用户选择具有不符合所需功能的无效扩展名的文件。 此外,由于应用程序的焦点是图像,因此,文件选取器的起始位置设置为图片库。 这可以是各种默认位置(如音乐库、文档库、家庭组等),但当处理图片时,起点理所当然是图片库。 文件选取器的最后一个设置是将 viewMode 设置为缩略图。 此设置显示文件的预览,对于选择图像是理想的设置。

设置选项后,就该为跑步选择要使用的文件了。 首先,在“use strict”语句的正下方添加以下两个变量声明:

var selectedPhotoStream = null;
var selectedPhotoFile = null;

在将数据加载到 Facebook 时,这些变量将为 btnAddRun_Click 函数容纳文件值和流值。 现在,使用图 5 中所示的代码替代“Pick file here”注释。

图 5 选取文件

filePicker.pickSingleFileAsync().then(
  function (storageFile) {
    if (storageFile) {
      selectedPhotoFile = storageFile;
      selectedPhotoFile.openAsync(
        Windows.Storage.FileAccessMode.read).then(
        function (stream) {
          selectedPhotoStream = stream;
        document.getElementById("imgSelectedPhoto").src =
          URL.createObjectURL(selectedPhotoFile);
      },
      function (ex) {
        Log(ex);
        Display("An error has occurred while reading the file.");
      });   
     }
    else {
      Display("File was not selected");
    }
  });

图 5 看起来是一些代码,但归根结底是三个操作:

  1. 选择应用程序将通过文件选取器使用的文件 (pickSingleFileAsync)。
  2. 打开要读取的文件流 (openAsync)。
  3. 将流和文件存储到变量中供以后使用。

所有代码对于处理文件和流都是标准的,但有一个例外: URL.createObjectURL 采用一个对象,并为该对象构建一个 URL,以便通过一个图像对象显示此对象。 createObjectURL 方法处理许多对象类型,包括 Stream、StorageItem 和 MediaCapture。 通常,此方法用于显示在 Windows 应用商店应用程序中显示媒体内容(图像、音频或视频)。 使用 createObjectURL 时要记住的一点是:当您完成使用 URL 时,确保通过 URL.revokeObjectURL 方法释放它。 这可确保内存使用达到最佳状态,并可防止 Windows 应用商店应用程序深陷于太多的临时 URL 中。 有关 createObjectURL 的详细信息,请查看 bit.ly/XdhzOm 上的 MSDN 文档。

最后,将 btnSelectPhoto_Click 事件与 btnSelectPhoto 对象相关联。 在 WinJS.UI.Pages.define { ready } 函数中添加以下代码:

var btnSelectPhoto = document.getElementById("btnSelectPhoto");
if (null != btnSelectPhoto)
  btnSelectPhoto.addEventListener(
    "click", btnSelectPhoto_Click, false);

此时,Contoso Photo Finish 有内容要发布,而我具有针对 Facebook 进行身份验证以进行发布的机制。 现在,应用程序只需与 API 交互和联机引发内容。

通过 XHR 获取和发布信息

您还记得 AJAX 是何时推陈出新并且变得如此令人激动的吗? XHR 最初面世是在 Internet Explorer 5.5 中,它使 Web 开发人员开始重新考虑如何开发 Web 应用程序。 随着时间推移,AJAX 也在不断成长,许多不同的库(如 jQuery)陆续纳入一个易于理解且易于实施(这一点更重要)的解决方案中。 WinJS.xhr 延续了这一传统,它通过一个简单的 API 获取数据并将数据发布到联机服务。

返回到 btnAddRun_Click 函数,并将“Add API calls here”注释替换为图 6 中所示的代码。

图 6 从 selectedPhotoStream 生成 Blob 对象

var fileBlob = MSApp.createBlobFromRandomAccessStream(
  selectedPhotoFile.contentType,
  selectedPhotoStream);
var message = "I just ran " + document.getElementById(
  "txtDistance").value + " miles with PhotoFinish! "
+ 
  document.getElementById("txtComment").value;
var data = new FormData();
data.append("source", fileBlob);
data.append("filename", selectedPhotoFile.
name);
data.append("access_token", token);
data.append("message", window.toStaticHTML(message));
WinJS.xhr({
  type: "POST",
  url: "https://graph.facebook.com/me/photos",
  data: data,
}).then(
  function (photoid_response) {
    ProcessResponse(photoid_response);
  },
  function (ex) {
    Display("An error occurred while posting the photo.");
    Log(ex);
  });

以前,应用程序将 StorageFile 和 Stream 存储到 selectedPhotoFile 和 selectedPhotoStream 变量中。 MSApp.createBlobFromRandomAccessStream 采用 selectedPhotoStream 并生成一个 Blob 对象 (bit.ly/Stfu9z),应用程序随后会将此对象作为 POST 参数传递到 Facebook。 这是一项很有用的功能,可将 WinRT 对象转换为可通过 HTTP 传输的格式。

接下来,代码使用 FormData 对象(HTML5 中新增的对象)创建将在 HTTP POST 中发送的参数。 FormData 是一个键/值对对象,只有一个方法: append。 使用 append 方法,开发人员可以动态构建表单字段,并使用 POST 提交这些字段,就好像调用了表单的提交事件一样。 FormData 也通过多部分/表单数据编码来提供参数,就好像这些参数属于某个表单一样。 这样,开发人员就可以向目标服务器发布任何输入类型(包括文件)了。

注意,在 FormData.append 方法调用中,我使用 toStatic­HTML (bit.ly/ZRKBka),以确保消息内容是安全的以便进行传递。 如果使用 toStaticHTML,则将首先从消息变量中删除任何事件属性或脚本内容,然后才将该消息变量添加到 FormData 对象。 尽管我设想 Facebook 可很好地防止跨网站脚本攻击(以及其他攻击),但作为混合 Web 应用程序开发人员,我希望为伙伴应用程序提供干净的内容。 Internet 是个非常大的场所,因此我们都需要彼此密切注意双方的应用程序。

代码块的剩余部分是 WinJS.xhr 调用。 此时此刻,代码使用 XHR 的一些更多属性,包括:

  • type(类型): 此属性设置要使用的 HTTP 方法。 默认情况下,类型设置为 GET。 此代码使用 POST,因为应用程序要向 Facebook API 发送内容。
  • data(数据): 此属性由要通过 POST 传递的参数组成。

当在 success 方法中返回承诺时,Contoso Photo Finish 处理照片 ID 供以后检索。 如果发生错误,将显示标准错误消息并记录异常。

WinJS.xhr 与其他 XHR 包装非常类似。 如果您熟悉 JavaScript 库(如 jQuery),则可以轻松地选取 WinJS.xhr。 您可能遇到的一个问题是 WinJS.xhr 方法没有超时选项,这一点与 XHR 不同。 设置超时是通过使用 WinJS.Promise.timeout 方法包装 WinJS.xhr 调用来实现的 (bit.ly/Qgtx7a)。 通过在 WinJS.xhr 调用的开头添加以下代码,我将针对 POST 的超时设置为 10 秒:

WinJS.Promise.timeout(1000, WinJS.xhr({ ...
});

如果 WinJS.xhr 承诺在 10 秒内未完成,承诺将超时,并通过超时承诺的错误函数来处理。

混合应用程序时的首要步骤

在本文中,我探讨了通过 WinJS 和 Windows 运行时进行身份验证、获取文件和发送数据的内容。 我们以这些基本技能为基础,可以设计出功能强大的 Windows 应用商店应用程序,而其功能仅受您的想像力和开发人员密钥所限。 请参阅本文中的材料,并了解您喜爱的联机服务。 使用 WinJS.xhr,您的 Windows 应用商店应用程序可以与联机提供的无数 API 进行交互。 借助于 Web 身份验证代理程序,您的应用程序可以通过 OAuth 或 OpenID,将用户与其联机个性、内容和社区相连接。 WinJS 和 Windows 运行时提供了各种工具,使您能够轻松地构建在功能上超过其所有联机服务总和的应用程序。

Tim Kulp 是位于美国巴尔的摩市的 FrontierMEDEX 公司开发小组的负责人。 有关他的情况,请访问 seccode.blogspot.com 上他的博客或访问 Twitter (Twitter.com/seccode),在上面他谈论了代码、安全性以及巴尔的摩市的美食、风景。

衷心感谢以下技术专家对本文的审阅: Sunil Gottumukkala 和 Jeremy Viegas