使用Jwt為.Net Core SignalR保駕護航

丶Pz發表於2018-09-19

前言

  SignalR Demo搭建起來之後,是沒有相應的認證和授權功能的。於是乎,參考官方文件實現了相應的邏輯。

簡單認證

  首先使用稍微簡單的方式實現。新增如下程式碼:

 services.AddAuthentication(auth =>
           {
                auth.DefaultScheme = "User";
           }).AddScheme<UserAuthenticationOptions, UserAuthenticationHandler>("User", o => { });

  

 public class UserAuthenticationOptions : AuthenticationSchemeOptions
    {
      
    }

  然後在新增Handler,重寫 AuthenticationHandler 的HandleAuthenticateAsync 方法

  public class UserAuthenticationHandler : AuthenticationHandler<UserAuthenticationOptions>
    {
        private readonly ILayIMUserFactory userFactory;

        public UserAuthenticationHandler(IOptionsMonitor<UserAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IServiceProvider serviceProvider)
        : base(options, logger, encoder, clock)
        {
            userFactory = serviceProvider.GetService<ILayIMUserFactory>();
        }
        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            var userId = userFactory.GetUserId(Request.HttpContext);
            if (string.IsNullOrEmpty(userId))
            {
                return Task.FromResult(AuthenticateResult.Fail("no user"));
            }
            var claims = new[] { new Claim("user", userId) };
            var identity = new ClaimsIdentity(claims, nameof(UserAuthenticationHandler));
            var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name);
            return Task.FromResult(AuthenticateResult.Success(ticket));
        }
    }

  最後在Hub上增加AuthorizeAttribute即可

 [Authorize(AuthenticationSchemes = “User”)]
 public class LayIMHub : Hub<ILayIMClient>{}

JWT Bearer認證

  首先安裝 Microsoft.AspNetCore.Authentication.JwtBearer .

  然後在Startup中增加如下程式碼:(基本上就是官方教程中的)

 services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters =
                    new TokenValidationParameters
                    {
                        LifetimeValidator = (before, expires, token, param) =>
                        {
                            return expires > DateTime.UtcNow;
                        },
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ValidateActor = false,
                        ValidateLifetime = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SignalRSecurityKey.TOKEN_KEY))
                    };
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var accessToken = context.Request.Query["access_token"];

                        var path = context.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) &&
                            (path.StartsWithSegments("/layimHub")))
                        {
                            context.Token = accessToken;
                        }
                        return Task.CompletedTask;
                    }
                };
            });

  不過AuthorizeAttribute 的Scheme 要改成  JwtBearerDefaults.AuthenticationScheme. 執行一下程式,因為剛開始沒有提供token,所以肯定是401的。

  

  對了,客戶端連線的時候要加上accessTokenFactory:

        var options = {};
            options.accessTokenFactory = () => token;
            //options.skipNegotiation = true;
            connection = new signalR.HubConnectionBuilder()
                .configureLogging(signalR.LogLevel.Trace)
                .withUrl(hubRoute, options)
                .withHubProtocol(protocol)
                .build();

  我們在實現一個獲取Token的介面,在呼叫SignalR連線之前,先獲取Token,然後把token帶上即可。以下程式碼是生成Token的方法,Subject的內容可以隨便定義

       var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(SignalRSecurityKey.TOKEN_KEY);
            var authTime = DateTime.UtcNow;
            var expiresAt = authTime.AddDays(7);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                   new Claim("uid",userId)
                }),
                Expires = expiresAt,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);
            return tokenString;

  下面在看演示:

  

  再次請求,已經帶上了token頭

  

  最後,在websocket握手階段,也會將這個token傳到後端。

 

 

   這樣我們的程式就能正常執行了。

總結

  本文只是一篇簡單的流水賬記錄。就醬吧

  程式碼地址:https://github.com/fanpan26/LayIM.AspNetCore/tree/master/src/LayIM.AspNetCore/LayIM.AspNetCore.IM.SignalR

 

相關文章