Web API / OWIN,SignalR和授权

28

我正在开发一个AngularJS、Web API、SignalR应用的原型,作为在VS 2013中启动新项目的潜在起点。

在这个阶段,我基本上使用Visual Studio生成的针对个人用户账户的示例代码。

在StartUp.Auth.cs代码中有一行看起来像这样的代码。

app.UseOAuthBearerTokens(OAuthOptions);

有了这个设置,我可以将 [Authorize] 属性添加到控制器中,它可以正常运行。

顺便说一下,在 Angular 中,我可以通过以下 JavaScript 添加一个包含令牌的标准头文件。

$http.defaults.headers.common.Authorization = 'bearer ' + access_token;

然后我将SignalR添加到该项目中。
它支持自己版本的[Authorize]属性,但是在使用SignalR时没有办法传递自定义标头。
这是浏览器端的限制。
文档说可以将令牌作为查询字符串的一部分传递。
我在JavaScript端添加了该代码。我的SignalR代码现在包括了这个。
我将令牌作为'bearer_token'传递。

this.connection = $.hubConnection("/TestHub", { useDefaultPath: false, qs: "bearer_token=" + token });
所以我的问题是如何让OWIN识别这个令牌,因为它不再在标题中了。
经过多次搜索,我最终添加了一些代码,将token从查询字符串移到标题中。
对于我的原型,我只是在StartUp.Auth.cs的原始代码上方添加了一小段代码。
所以,现在它看起来像这样:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
    Provider = new OAuthBearerAuthenticationProvider()
    {
        OnRequestToken = context =>
        {
            if (context.Request.Path.Value.StartsWith("/TestHub"))
            {
                string bearerToken = context.Request.Query.Get("bearer_token");
                if (bearerToken != null)
                {
                    string[] authorization = new string[] { "bearer " + bearerToken };
                    context.Request.Headers.Add("Authorization", authorization);
                }
            }

            return Task.FromResult(context);
        }
    }
});


// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);

上面的代码比较粗糙,但这只是一个原型,我只是想看看它是否能够工作,而它确实可以。

最后来到问题: 这是否是将Bearer Token授权与SignalR和OWIN管道集成的正确模式?
我似乎找不到关于正确操作方式的好信息。


现在它是否正常工作?这可能是我见过的最好的尝试。 - AD.Net
2
它运行良好。只是需要一点指导来了解如何处理它。谢谢。 - Peter Trenery
1
我做了类似的事情,但我已经将其封装在了一个OWIN中间件模块中。 - eth0
奇怪的是设置标题对我没有任何作用...我必须将Cookie值直接放入上下文中...如果(!string.IsNullOrEmpty(bearerToken)){context.Token = bearerToken;} - KingOfHypocrites
3个回答

19
我使用类似这样的类:
public class OAuthTokenProvider : OAuthBearerAuthenticationProvider
{
    private List<Func<IOwinRequest, string>> _locations;
    private readonly Regex _bearerRegex = new Regex("((B|b)earer\\s)");
    private const string AuthHeader = "Authorization";

    /// <summary>
    /// By Default the Token will be searched for on the "Authorization" header.
    /// <para> pass additional getters that might return a token string</para>
    /// </summary>
    /// <param name="locations"></param>
    public OAuthTokenProvider(params Func<IOwinRequest, string>[] locations)
    {
        _locations = locations.ToList();
        //Header is used by default
        _locations.Add(x => x.Headers.Get(AuthHeader));
    }

    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var getter = _locations.FirstOrDefault(x => !String.IsNullOrWhiteSpace(x(context.Request)));
        if (getter != null)
        {
            var tokenStr = getter(context.Request);
            context.Token = _bearerRegex.Replace(tokenStr, "").Trim();
        }
        return Task.FromResult<object>(null);
    }
}

不仅将令牌传递到标头,而是解析并将其设置在上下文中。

然后可以在您的应用程序配置中使用它,如下所示:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
    Provider = new OAuthTokenProvider(
         req => req.Query.Get("bearer_token"),
         req => req.Query.Get("access_token"),
         req => req.Query.Get("token"),
         req => req.Headers.Get("X-Token"))    
});

接下来,以下类型的请求将读取其令牌,以便用于身份验证和/或授权:

GET https://www.myapp.com/authorized/endpoint?bearer_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?access_token=123ABC HTTP/1.1
GET https://www.myapp.com/authorized/endpoint?token=123ABC HTTP/1.1

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
X-Token: 123ABC

GET https://www.myapp.com/authorized/endpoint HTTP/1.1
Authorization: 123ABC

1
+1 你能否展示更多的代码,特别是config/startup代码和客户端代码? - AD.Net
这有帮助吗?对于使用SignalR的JS客户端,我认为应该是这样的: jQuery.hubConnection(url,{qs:"access_token="+token}) - calebboyd
@calebboyd +1 谢谢,这正是我想要的。你是如何设置 X-TokenAuthorization 头部的? - GFoley83
1
在令牌端点的响应处理程序中,设置一个默认标头...具体细节可能取决于您的xhr抽象... - calebboyd
3
非常优雅的解决方案!谢谢! - Chris Woolum

16

这是我如何解决它的方法

var authData = localStorageService.get('authorizationData');
var token = authData.token;
 $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

服务器端代码无需更改

希望能对某些人有所帮助


3
这是否与 Winsock 上 JavaScript 客户端无法添加自定义标头的事实相矛盾呢?(根据我在 Stack Overflow 上阅读的许多内容)如果是这样,这将适用于长轮询和 Web 事件,但不适用于 Websocket 传输。 - pomarc
@pomarc 你确认过这个吗?我读到了类似的东西。 - KingOfHypocrites
11
它可以工作,但仅适用于SignalR中的“长轮询”连接类型。如果想要在WebSockets中使用OAuth承载令牌身份验证,您应该通过查询参数传递承载令牌,并实现自定义的OAuthBearerAuthenticationProvider,用于从查询参数中提取您的令牌。在这里查看:https://dev59.com/_4Tba4cB1Zd3GeqP7YmU - Bounz
值得注意的是,使用查询参数中的令牌可能存在轻微的安全风险,因为它很可能会被记录在服务器上。 - Bon
@KingOfHypocrites - Pomarc所说的是真的。我测试过了。使用Bounz添加的链接。 - Tohid
这将强制 SignalR 使用长轮询。 - radu-matei

14

我会把这个问题发布出来,以便日后遇到同样问题的人可以参考:

有多种方法可以解决它,但这取决于应用程序的目的。

  1. 我见过的第一种方法是让 SignalR 与头文件一起工作,这似乎很容易实现:

    $.signalR.ajaxDefaults.headers = { Authorization: "Bearer " + token };

这种方法的巨大缺点是它强制使用 longPolling,您绝对不希望这样做,因为您正在使用 SignalR。

  1. 第二种方法是在连接之前作为查询字符串传递客户端登录时接收到的 access token。然后,创建一个自定义的 Attribute,该属性继承自 AuthorizeAttribute (参照此 repo - 我个人认为不太好,但可以说明问题)。

另一种将令牌作为查询字符串传递的方法是 遵循此 SO 答案,它创建了一个 OAuth 提供程序

在管道的早期检索令牌中的所有其他值

同样,这种方法不是最佳方法,因为 查询字符串 是非常容易受攻击的:

查询字符串往往会存储在 web 服务器日志中(即使使用 SSL 也会暴露)。有人截取令牌的风险。您需要选择最适合您情况的方法。

  1. 我目前正在尝试实施的解决方案(一旦它起作用,我将回来更新:D)是基于这篇博客文章,它使用 SignalROAuth Bearer Token 认证,并通过 cookie 将令牌传递到 SignalR 管道。

我认为这是最少妥协的解决方案,但是一旦我的实现完成,我将回来提供更多信息。

希望这可以帮到你。祝你好运!

更新 我通过将令牌存储在 cookie 中,然后在提供程序中检索它,成功使 WebApi 令牌验证与 SignalR 配合使用。

您可以查看此 GitHub repo,但我大多数时间都遵循上面的文章。具体而言,我做了以下事情:

创建了一个继承自 OAuthBearerAuthenticationProviderOAuthBearerTokenAuthenticationProvider 类,并重写了 RequestToken 方法。

现在它会查找存储 BearerToken 的 cookie,并检索其值。然后,它将 context.Token 设置为 cookie 的值。

然后

public class OAuthBearerTokenAuthenticationProvider : OAuthBearerAuthenticationProvider
{
    public override Task RequestToken(OAuthRequestTokenContext context)
    {
        var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];

        if (!String.IsNullOrEmpty(tokenCookie))
            context.Token = tokenCookie;

        return Task.FromResult<object>(null);
    }
}

如需一个可运行的示例,请查看上方的存储库。

祝你好运!


你对这个有什么运气或者成功的经验吗? - overslacked
1
最终我不得不放弃承载令牌身份验证,转而使用cookie身份验证。总体来说,这只是更简单的解决方案。 - radu-matei
谢谢您的回复! - overslacked
@overslacked 我更新了我的回答,并添加了可用的 GitHub 存储库。 - radu-matei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接