简体   繁体   English

Web API / OWIN,SignalR和授权

[英]Web API / OWIN, SignalR & Authorization

I am developing a prototype of an AngularJS, Web API, SignalR application as a potential starting point for a new project in VS 2013. 我正在开发AngularJS,Web API,SignalR应用程序的原型,作为VS 2013中新项目的潜在起点。

At this stage, I'm pretty much using the canned code that visual studio generates for Individual User Accounts. 在这个阶段,我几乎使用visual studio为个人用户帐户生成的预制代码。

There's a line in the StartUp.Auth.cs code that looks like this. StartUp.Auth.cs代码中有一行看起来像这样。

app.UseOAuthBearerTokens(OAuthOptions);

With this in place, I can add the [Authorize] attribute to controllers and it works fine. 有了这个,我可以将[Authorize]属性添加到控制器,它工作正常。

Incidentally, with angular I was able to add a standard header containing the token in the JavaScript as follows. 顺便说一句,使用angular我可以在JavaScript中添加包含令牌的标准头,如下所示。

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

Then I added SignalR to the project. 然后我将SignalR添加到项目中。
It supports it's own version of the [Authorize] attribute but there is no way to pass custom headers when using SignalR. 它支持自己的[Authorize]属性版本,但在使用SignalR时无法传递自定义标头。
It's a limitation on the browser side. 这是浏览器方面的限制。
The documentation said you could pass the token as part of the query string. 文档说您可以将令牌作为查询字符串的一部分传递。
I added that code on the JavaScript side. 我在JavaScript方面添加了该代码。 My SignalR code now included this. 我的SignalR代码现在包含了这个。
I passed the token as 'bearer_token'. 我将令牌传递为'bearer_token'。

this.connection = $.hubConnection("/TestHub", { useDefaultPath: false, qs: "bearer_token=" + token });

So my issue was how to make OWIN recognize the token now it was no longer in the header. 所以我的问题是如何使OWIN识别令牌现在它不再在标题中。
After a number of searches, I ended up adding code that moved the token from the querystring into the header. 经过多次搜索后,我最终添加了将令牌从查询字符串移动到标题中的代码。
For my prototype, I just added a little code above the original line in StartUp.Auth.cs. 对于我的原型,我刚刚在StartUp.Auth.cs的原始行上面添加了一些代码。

So, now it looked like this: 所以,现在它看起来像这样:

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);

The code above is rough but this is a prototype so really I just wanted to see if it worked which it did. 上面的代码是粗略的,但这是一个原型,所以我只是想看看它是否有效。

Finally getting to the question: Is this the right pattern for integrating bearer token authorization with SignalR and the OWIN pipeline. 最后回答问题:这是将承载令牌授权与SignalR和OWIN管道集成的正确模式。
I couldn't seem to find much good information on the right way to do this. 我似乎无法找到正确的方法来做到这一点。

I use a class like this: 我使用这样的类:

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);
    }
}

Which instead of just passing on the token to the header, parses it and sets it on the context. 而不是仅仅将令牌传递给标头,解析它并在上下文中设置它。

Then it could be used in your app configuration like this: 然后它可以在您的应用程序配置中使用,如下所示:

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"))    
});

Then the following styles of requests would have their token un-encrypted, for use with authentication and authorization: 然后,以下样式的请求将使其令牌未加密,以用于身份验证和授权:

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

This how I solved it 这是我如何解决它

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

No change on the server side code 服务器端代码没有变化

Hope it helps someone 希望它可以帮助某人

I'll post this for the the people that will have this issue in the future: 我将为将来会遇到此问题的人发布此信息:

There are multiple approaches to this in order to just make it work, but depending on the purpose of the application. 有多种方法可以使它工作,但取决于应用程序的目的。

  1. The first that I've seen makes SignalR work with headers, which would seem very easy to implement: 我见过的第一个使SignalR与头文件SignalR工作,这看起来很容易实现:

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

The huge downside of this is that it forces SignalR to use longPolling , which you most definitely don't want, since you are using SignalR. 这个的巨大缺点是它迫使SignalR使用longPolling ,你绝对不想要,因为你使用的是SignalR。

  1. The second approach is to pass the access token that the client receives when logging in as a query string , right before connecting. 第二种方法是在连接之前传递客户端在作为query string登录时收到的access token Then, make a custom Attribute that inherits the AuthorizeAttribute ( follow this repo - not great in my opinion, but it makes a point ). 然后,创建一个继承AuthorizeAttribute的自定义Attribute遵循此repo - 在我看来并不是很好,但它提出了一个观点 )。

Another approach with passing the token as query string is to follow this SO answer which creates an OAuth Provider and 将令牌作为query string传递的另一种方法是遵循此SO答案该答案创建OAuth Provider

retrieves all other values from the token early in the pipeline 在管道的早期检索令牌中的所有其他值

Again, this method is not the optimal one since query strings are pretty vulnerable: 同样,此方法不是最佳方法,因为query strings非常容易受到攻击:

query strings tend to be stored in web server logs (or exposed in other ways even when using SSL). 查询字符串往往存储在Web服务器日志中(或者即使在使用SSL时也以其他方式公开)。 There is a risk of someone intercepting the tokens. 有人拦截令牌的风险。 You need to select an approach that fits your scenario best. 您需要选择最适合您的方案的方法。

  1. The solution I am currently trying to implement (and will come back with updates once it works:D) is based on this blog post which uses the OAuth Bearer Token authentication with SignalR by passing the token over a cookie into SignalR pipeline. 我目前正在尝试实施的解决方案(并且一旦工作将返回更新:D) 基于此博客文章该文章通过将令牌通过cookie传递到SignalR管道,使用与SignalROAuth Bearer Token authentication

I believe this is the solution with the fewest compromises, but I will come back with more information once my implementation is complete. 我相信这是最少妥协的解决方案,但是一旦我的实施完成,我会回来提供更多信息。

Hope this helps. 希望这可以帮助。 Best of luck! 祝你好运!

UPDATE I managed to get WebApi token authentication to work with SignalR by storing the token in a cookie, then retrieve it in a provider. 更新我通过将令牌存储在cookie中,然后在提供程序中检索它,设法通过WebApi令牌身份验证来使用SignalR。

You cah take a look at this GitHub repo , but I mostly followed the article from above. 你可以看看这个GitHub回购 ,但我大多数都是按照上面的文章进行的。 Explicitly, here's what I did: 明确地说,这就是我所做的:

I created an OAuthBearerTokenAuthenticationProvider class that inherits from OAuthBearerAuthenticationProvider and overridden the RequestToken method. 我创建了一个OAuthBearerTokenAuthenticationProvider类,它继承自OAuthBearerAuthenticationProvider并重写了RequestToken方法。

Now it looks for the cookie where the BearerToken is stored and retrieves its value. 现在它查找存储BearerToken的cookie并检索其值。 Then, it sets the context.Token to the value of the cookie. 然后,它将context.Token设置为cookie的值。

Then, on the JavaScript part, you have to get the token (by authenticating using the user name and password) and store the token in a cookie. 然后,在JavaScript部分,您必须获取令牌(通过使用用户名和密码进行身份验证)并将令牌存储在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);
    }
}

For a working sample, please take a look at the repo above. 如需工作样本,请查看上面的回购。

Best of luck! 祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM