SignalR authentication with webAPI Bearer Token

匿名 (未验证) 提交于 2019-12-03 02:45:02

问题:

+i used this solution to implement Token Based Authentication using ASP.NET Web API 2, Owin, and Identity...which worked out excellently well. i used this other solution and this to implement signalR hubs authorization and authentication by passing the bearer token through a connection string, but seems like either the bearer token is not going, or something else is wrong somewhere, which is why am here seeking HELP...these are my codes... QueryStringBearerAuthorizeAttribute: this is the class in charge of verification

using ImpAuth.Entities; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using Microsoft.Owin.Security.OAuth; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web;  namespace ImpAuth.Providers {     using System.Security.Claims;     using Microsoft.AspNet.SignalR;     using Microsoft.AspNet.SignalR.Hubs;     using Microsoft.AspNet.SignalR.Owin;      public class QueryStringBearerAuthorizeAttribute : AuthorizeAttribute     {         public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)         {             var token = request.QueryString.Get("Bearer");             var authenticationTicket = Startup.AuthServerOptions.AccessTokenFormat.Unprotect(token);              if (authenticationTicket == null || authenticationTicket.Identity == null || !authenticationTicket.Identity.IsAuthenticated)             {                 return false;             }              request.Environment["server.User"] = new ClaimsPrincipal(authenticationTicket.Identity);             request.Environment["server.Username"] = authenticationTicket.Identity.Name;             request.GetHttpContext().User = new ClaimsPrincipal(authenticationTicket.Identity);             return true;         }          public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)         {             var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;              // check the authenticated user principal from environment             var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;             var principal = environment["server.User"] as ClaimsPrincipal;              if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)             {                 // create a new HubCallerContext instance with the principal generated from token                 // and replace the current context so that in hubs we can retrieve current user identity                 hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);                  return true;             }              return false;         }     } } 

and this is my start up class....

using ImpAuth.Providers; using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Cors; using Microsoft.Owin.Security.Facebook; using Microsoft.Owin.Security.Google; //using Microsoft.Owin.Security.Facebook; //using Microsoft.Owin.Security.Google; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Http;  [assembly: OwinStartup(typeof(ImpAuth.Startup))]  namespace ImpAuth {     public class Startup     {         public static OAuthAuthorizationServerOptions AuthServerOptions;          static Startup()         {             AuthServerOptions = new OAuthAuthorizationServerOptions             {                 AllowInsecureHttp = true,                 TokenEndpointPath = new PathString("/token"),                 AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),                 Provider = new SimpleAuthorizationServerProvider()                // RefreshTokenProvider = new SimpleRefreshTokenProvider()             };         }          public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }         public static GoogleOAuth2AuthenticationOptions googleAuthOptions { get; private set; }         public static FacebookAuthenticationOptions facebookAuthOptions { get; private set; }          public void Configuration(IAppBuilder app)         {             //app.MapSignalR();             ConfigureOAuth(app);             app.Map("/signalr", map =>             {                 // Setup the CORS middleware to run before SignalR.                 // By default this will allow all origins. You can                  // configure the set of origins and/or http verbs by                 // providing a cors options with a different policy.                 map.UseCors(CorsOptions.AllowAll);                 var hubConfiguration = new HubConfiguration                 {                     // You can enable JSONP by uncommenting line below.                     // JSONP requests are insecure but some older browsers (and some                     // versions of IE) require JSONP to work cross domain                     //EnableJSONP = true                     EnableDetailedErrors = true                 };                 // Run the SignalR pipeline. We're not using MapSignalR                 // since this branch already runs under the "/signalr"                 // path.                 map.RunSignalR(hubConfiguration);             });             HttpConfiguration config = new HttpConfiguration();             app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);             WebApiConfig.Register(config);             app.UseWebApi(config);         }          public void ConfigureOAuth(IAppBuilder app)         {             //use a cookie to temporarily store information about a user logging in with a third party login provider             app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);             OAuthBearerOptions = new OAuthBearerAuthenticationOptions();              OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()             {                 AllowInsecureHttp = true,                 TokenEndpointPath = new PathString("/token"),                 AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),                 Provider = new SimpleAuthorizationServerProvider()             };              // Token Generation             app.UseOAuthAuthorizationServer(OAuthServerOptions);             app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());              //Configure Google External Login             googleAuthOptions = new GoogleOAuth2AuthenticationOptions()             {                 ClientId = "1062903283154-94kdm6orqj8epcq3ilp4ep2liv96c5mn.apps.googleusercontent.com",                 ClientSecret = "rv5mJUz0epWXmvWUAQJSpP85",                 Provider = new GoogleAuthProvider()             };             app.UseGoogleAuthentication(googleAuthOptions);              //Configure Facebook External Login             facebookAuthOptions = new FacebookAuthenticationOptions()             {                 AppId = "CHARLIE",                 AppSecret = "xxxxxx",                 Provider = new FacebookAuthProvider()             };             app.UseFacebookAuthentication(facebookAuthOptions);         }     }  } 

and this is the knockout plus jquery code on the client....

function chat(name, message) {     self.Name = ko.observable(name);     self.Message = ko.observable(message); }  function viewModel() {     var self = this;     self.chatMessages = ko.observableArray();      self.sendMessage = function () {         if (!$('#message').val() == '' && !$('#name').val() == '') {             $.connection.hub.qs = { Bearer: "yyCH391w-CkSVMv7ieH2quEihDUOpWymxI12Vh7gtnZJpWRRkajQGZhrU5DnEVkOy-hpLJ4MyhZnrB_EMhM0FjrLx5bjmikhl6EeyjpMlwkRDM2lfgKMF4e82UaUg1ZFc7JFAt4dFvHRshX9ay0ziCnuwGLvvYhiriew2v-F7d0bC18q5oqwZCmSogg2Osr63gAAX1oo9zOjx5pe2ClFHTlr7GlceM6CTR0jz2mYjSI" };             $.connection.hub.start().done(function () {                 $.connection.hub.qs = { Bearer: "yyCH391w-CkSVMv7ieH2quEihDUOpWymxI12Vh7gtnZJpWRRkajQGZhrU5DnEVkOy-hpLJ4MyhZnrB_EMhM0FjrLx5bjmikhl6EeyjpMlwkRDM2lfgKMF4e82UaUg1ZFc7JFAt4dFvHRshX9ay0ziCnuwGLvvYhiriew2v-F7d0bC18q5oqwZCmSogg2Osr63gAAX1oo9zOjx5pe2ClFHTlr7GlceM6CTR0jz2mYjSI" };                 $.connection.impAuthHub.server.sendMessage($('#name').val(), $('#message').val())                             .done(function () { $('#message').val(''); $('#name').val(''); })                             .fail(function (e) { alert(e) });             });         }     }      $.connection.impAuthHub.client.newMessage = function (NAME, MESSAGE) {         //alert(ko.toJSON(NAME, MESSAGE));         var chat1 = new chat(NAME, MESSAGE);         self.chatMessages.push(chat1);     }  }  ko.applyBindings(new viewModel()); 

and here is my hub class...

using ImpAuth.Providers; using Microsoft.AspNet.SignalR; using System; using System.Collections.Generic; using System.Linq; using System.Web;  namespace ImpAuth {     public class impAuthHub : Hub     {         [QueryStringBearerAuthorize]         public void SendMessage(string name, string message)         {              Clients.All.newMessage(name, message);         }     } } 

...now the problem comes when i try to invoke an authenticated hub class and i get this error

caller is not authenticated to invove method sendMessage in impAuthHub 

but then i change this method in QueryStringBearerAuthorizeAttribute class to alway return true like this

public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod) {     var connectionId = hubIncomingInvokerContext.Hub.Context.ConnectionId;     // check the authenticated user principal from environment     var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;     var principal = environment["server.User"] as ClaimsPrincipal;      if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated)     {         // create a new HubCallerContext instance with the principal generated from token         // and replace the current context so that in hubs we can retrieve current user identity         hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), connectionId);          return true;     }      return true; } 

...it works....WHAT IS THE PROBLEM WITH MY CODE OR IMPLEMENTATION?

回答1:

You need to configure your signalr like this;

app.Map("/signalr", map => {     map.UseCors(CorsOptions.AllowAll);      map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()     {         Provider = new QueryStringOAuthBearerProvider()     });      var hubConfiguration = new HubConfiguration     {         Resolver = GlobalHost.DependencyResolver,     };     map.RunSignalR(hubConfiguration); }); 

Then you need to write a basic custom OAuthBearerAuthenticationProvider for signalR which accepts access_token as query string.

public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider {     public override Task RequestToken(OAuthRequestTokenContext context)     {         var value = context.Request.Query.Get("access_token");          if (!string.IsNullOrEmpty(value))         {             context.Token = value;         }          return Task.FromResult<object>(null);     } } 

After this all you need is to send access_token with signalr connection as querystring.

$.connection.hub.qs = { 'access_token': token }; 

And for your hub just ordinary [Authorize] attribute

public class impAuthHub : Hub {     [Authorize]     public void SendMessage(string name, string message)     {        Clients.All.newMessage(name, message);     } } 

Hope this helps. YD.



回答2:

Can't comment so adding my answer after the comments on Peter's excellent answer.

Did a bit more digging and the user id that I had set in my custom owin authorization provider was hiding here (complete hub method shown).

    [Authorize]     public async Task<int> Test()     {         var claims = (Context.User.Identity as System.Security.Claims.ClaimsIdentity).Claims.FirstOrDefault();         if (claims != null)         {             var userId = claims.Value;              //security party!             return 1;         }          return 0;     } 

More added for texas697:

Startup.Auth.cs add this to ConfigureAuth() if not already there:

app.Map("/signalr", map =>     {         map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()         {             Provider = new QueryStringOAuthBearerProvider() //important bit!         });          var hubConfiguration = new HubConfiguration         {             EnableDetailedErrors = true,             Resolver = GlobalHost.DependencyResolver,         };         map.RunSignalR(hubConfiguration);     }); 

The custom auth provider looks like this:

public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider {     public override Task RequestToken(OAuthRequestTokenContext context)     {         var value = context.Request.Query.Get("access_token");          if (!string.IsNullOrEmpty(value))         {             context.Token = value;         }          return Task.FromResult<object>(null);     } } 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!