如何在 Javascript 中实现安全的 OAuth2 消费?

Posted

技术标签:

【中文标题】如何在 Javascript 中实现安全的 OAuth2 消费?【英文标题】:How do I implement secure OAuth2 consumption in Javascript? 【发布时间】:2012-07-11 12:40:38 【问题描述】:

我正在用 php 设计一个将使用 OAuth2.0 的 API。我的最终目标是用 javascript(使用 AngularJS)构建一个直接访问这个 API 的前端应用程序。我知道传统上没有办法保护 javascript 中的事务,因此直接访问 API 是不可行的。前端需要与服务器代码通信,而服务器代码又直接与 API 通信。但是,在研究 OAuth2 时,似乎用户代理流程旨在帮助解决这种情况。

我需要帮助的是在 javascript 中实现 OAuth2 用户代理流程(特别是 AngularJS,如果可能的话,因为这是我在前端使用的)。我还没有找到任何可以做到这一点的示例或教程。我真的不知道从哪里开始,也不想阅读整个 OAuth2 规范,至少没有看到我将要做什么的例子。因此,任何示例、教程、链接等都将不胜感激。

【问题讨论】:

通过关于 SO 的相关问题,我发现这个 JS 库似乎可以简化和处理我正在尝试做的事情。 github.com/andreassolberg/jso 它似乎很小且未使用。另外,它并不能完全解决我的问题。 你发现了吗? @DavidMyers 您在执行此 Javascript 时是否收到了 CORS 错误。你是怎么解决的 【参考方案1】:

Implicit Grant flow(您称为 User-Agent 流)正是要走的路:

隐式授权是一种简化的授权代码流,针对使用 JavaScript 等脚本语言在浏览器中实现的客户端进行了优化。

要了解流程,Google 的 client-side applications 文档是一个非常好的起点。请注意,他们建议您采取额外的令牌验证步骤以避免confused deputy problems。

这是使用 Soundcloud API 和 jQuery 的流程的简短示例实现,取自 this answer:

<script type="text/javascript" charset="utf-8">
  $(function () 
    var extractToken = function(hash) 
      var match = hash.match(/access_token=([\w-]+)/);
      return !!match && match[1];
    ;

    var CLIENT_ID = YOUR_CLIENT_ID;
    var AUTHORIZATION_ENDPOINT = "https://soundcloud.com/connect";
    var RESOURCE_ENDPOINT = "https://api.soundcloud.com/me";

    var token = extractToken(document.location.hash);
    if (token) 
      $('div.authenticated').show();

      $('span.token').text(token);

      $.ajax(
          url: RESOURCE_ENDPOINT
        , beforeSend: function (xhr) 
            xhr.setRequestHeader('Authorization', "OAuth " + token);
            xhr.setRequestHeader('Accept',        "application/json");
          
        , success: function (response) 
            var container = $('span.user');
            if (response) 
              container.text(response.username);
             else 
              container.text("An error occurred.");
            
          
      );
     else 
      $('div.authenticate').show();

      var authUrl = AUTHORIZATION_ENDPOINT + 
        "?response_type=token" +
        "&client_id="    + clientId +
        "&redirect_uri=" + window.location;

      $("a.connect").attr("href", authUrl);
    
  );
</script>
<style>
  .hidden 
    display: none;
  
</style>

<div class="authenticate hidden">
  <a class="connect" href="">Connect</a>
</div>

<div class="authenticated hidden">
  <p>
    You are using token
    <span class="token">[no token]</span>.
  </p>

  <p>
    Your SoundCloud username is
    <span class="user">[no username]</span>.
  </p>
</div>

关于使用 AngularJS 发送 XMLHttpRequests(ajax() 函数在 jQuery 中的作用),请参阅他们的 $http service 文档。

如果您想保留状态,在将用户发送到授权端点时,请查看state 参数。

【讨论】:

要使用最新的 SoundCloud api,请将正则表达式更改为 /access_token=([\w-]+)/。他们的标记现在包括破折号字符。 谢谢,我编辑了答案。可能还允许使用其他字符,但我不确定这是否记录在任何地方。 @Ben 隐式和身份验证代码授予的优点之一是客户端应用程序(不管是第一方还是第三方)不必担心存储纯文本凭据用户。这是典型的权衡安全性可用性。 @Ben 好吧,您冒着某些恶意客户端使用您的客户端凭据来欺骗用户的风险。取决于您的用例是否重要。您可以在OAuth 2 Threat Model docs 中找到一些其他注意事项。 不应该是xhr.setRequestHeader('Authorization', "Bearer " + token)吗?【参考方案2】:

有一个使用Authorization Code Grant 方法从 OAuth 服务器获取令牌的示例。我用jQuery($)做了一些操作。

首先,将用户重定向到授权页面。

var authServerUri = "http://your-aouth2-server.com/authorize",
authParams = 
  response_type: "code",
  client_id: this.model.get("clientId"),
  redirect_uri: this.model.get("redirectUri"),
  scope: this.model.get("scope"),
  state: this.model.get("state")
;

// Redirect to Authorization page.
var replacementUri = authServerUri + "?" + $.param(authParams);
window.location.replace(replacementUri);

当一个人通过授权获得令牌时:

var searchQueryString = window.location.search;
if ( searchQueryString.charAt(0) === "?") 
  searchQueryString = searchQueryString.substring(1);

var searchParameters = $.deparam.fragment(searchQueryString);

if ( "code" in searchParameters) 
  // TODO: construct a call like in previous step using $.ajax() to get token.

您可以使用 jQuery 或纯 XMLHttpRequest 以相同的方式实现 Resource Owner Password Credentials Grant,并且不要进行任何重定向 - 因为在每次重定向时,您都会失去应用程序的状态。

对我来说,我使用 html5 本地存储来保存不太可能构成安全威胁的数据的应用程序状态。

【讨论】:

在这种情况下,使用 授权码 流程不会比 隐式授权 流程带来额外的好处。此外,如果 API 支持更多(隐藏秘密)和更少(公开秘密)受信任客户端的不同授权流程,则服务器可以区分它们并执行例如额外的检查或限制范围。此外,在这种情况下不应使用 资源所有者密码凭证 流程,因为资源所有者不能从第三方网站获得 high degree of trust 到 JS 客户端代码。 你指的是哪个重定向?授权端点的那个?该重定向也是 implicit grant 流程的一部分。 这是个坏主意。如果您重视用户和您自己的安全,则永远不应在移动设备或客户端应用程序中使用授权授权。使用此系统,您必须将 OAuth2 客户端密码存储在应用程序中黑客可以轻松找到的位置(例如,在源代码中)。如果您要在请求中包含刷新令牌,那么您可能会将用户数据的基本永久密钥交给任何只有少量技能的黑客。 @MichaelOryl 根据规范,您不需要为密码授予提供客户端密码。您的 oauth 服务器应使用列入白名单的 URL 保护客户端,以防止非法使用客户端。 +1,因为最近记录了客户端代码的最佳实践:tools.ietf.org/html/…

以上是关于如何在 Javascript 中实现安全的 OAuth2 消费?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中实现 Base64 URL 安全编码?

如何在 javascript 中实现 Random.nextDouble()?

如何在 Wordpress 中实现 javascript

如何在火花聚合函数中实现scala类型安全

如何在javascript中实现区域/代码折叠

如何在javascript中实现分层多级数据表?