.Net Core 3.1 SignalR 客户端 - 如何将 JWT 令牌字符串添加到 SignalR 连接配置?

.Net Core 3.1 SignalR Client - How to add the JWT Token string to SignalR connection configuration?

我在带有 JWT 令牌的项目中使用 SignalR .net 核心客户端。

在下面的示例代码中,字符串变量 "tokenString" 已经被配置为一个实际的令牌,因此我不需要调用外部方法来创建令牌,那部分已经在我到达这个方法之前完成。使用调试,并在 JWT 网站上测试 "toeknString" 值,我知道令牌正在工作,只是我不知道如何在 SignalR 连接方法中使用现成的令牌。

如何配置 SignalR 客户端连接以使用此 tokenString?

localConConnection = new HubConnectionBuilder()
                .WithUrl("https://localhost:44372/LocalConnectorHub", options => 
                {
                    options.AccessTokenProvider = () => Task.FromResult(tokenString); // Not working
                    // Need a solution like this: options.Token = tokenString
                })
                .WithAutomaticReconnect()
                .Build();

问题是我在 SignalR Hub class 中配置的 [Authorize] 属性需要定义要使用的身份验证方案,仅 [Authorize] 属性是不够的。

SignalR 中心 Class:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class LocalConnectorHub : Hub
{
    public async Task SendToMacros(string serverName, string data)
    {
        await Clients.All.SendAsync("MacrosInbound", serverName, data);
    }

    
    public async Task ConnectorStatus(string serverName, string data)
    {
        await Clients.All.SendAsync("UpdateConnectorStatus", serverName, data);
    }
}

SignalR .NET Core 客户端连接:

localConConnection = new HubConnectionBuilder()
                .WithUrl("https://localhost:44372/LocalConnectorHub", options => 
                {
                    options.AccessTokenProvider = () => Task.FromResult(tokenString); 
                })
                .WithAutomaticReconnect()
                .Build();

await localConConnection.StartAsync();

来自 startup.cs class(内部配置服务方法)的更多示例代码,发布此代码是为了在下面的评论中帮助我们的一位同事:

// Retrieve the secret key from the appsettings.json file used for encryption 
// when generating the JWT token for REST API authentication.
var key = Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Token").Value);

// Added to original .net core template.
// The preceding code configures multiple authentication methods. The app uses cookie-based authentication to log in
// via the browser using the identity manager. The second methid uses JWT bearer authentication for the REST API.
// The preceding cookie configuration configures Identity with default option values. 
// Services are made available to the app through dependency injection.
// Cookie configuration MUST be called after calling AddIdentity or AddDefaultIdentity.
// IMPORTANT NOTE: 
// When we decorate controllers or classes with use the [Authorize] attribute, it actually binds to the first authentication 
// system by default (in this case cookie authentication) The trick is to change the attribute to specify which authorization
// service we want to use. Anexample for a protected respurce for a REST API controller would be to decorate using:
// "[Authorize(AuthenticationSchemes =  JwtBearerDefaults.AuthenticationScheme)]"
services.AddAuthentication()
.AddCookie(options =>
{
    // Cookie settings
    options.Cookie.Name = "MyCompanyName";

    // HttpOnly is a flag that can be used when setting a cookie to block access to the cookie from client side scripts. 
    // Javascript for example cannot read a cookie that has HttpOnly set. This helps mitigate a large part of XSS attacks 
    // as many of these attempt to read cookies and send them back to the attacker, possibly leaking sensitive information 
    // or worst case scenario, allowing the attacker to impersonate the user with login cookies.
    options.Cookie.HttpOnly = true;

    // CookieAuthenticationOptions.ExpireTimespan is the option that allows you to set how long the issued cookie is valid for.
    // The cookie is valid for (XX) minutes from the time of creation. Once those XX minutes are up the user will have to sign 
    // back in becuase if the SlidingExpiration is set to false.
    // If SlidingExpiration is set to true then the cookie would be re-issued on any request half way through the ExpireTimeSpan. 
    // For example, if the user logged in and then made a second request half way through the permitted timespan then the cookie 
    // would be re-issued for another (XX) minutes. If the user logged in and then made a second request AFTER (XX) minutes later 
    // then the user would be prompted to log in.
    // You can also change the units i.e. TimeSpan.FromHours(10); OR TimeSpan.FromDays(10);
    // In a nutshell, setting the options.ExpireTimeSpan is equivalent to setting an idle time out period...
    options.ExpireTimeSpan = TimeSpan.FromMinutes(10);

    options.LoginPath = "/Identity/Account/Login";
    options.AccessDeniedPath = "/Identity/Account/AccessDenied";

    // Sliding expiration resets the expiration time for a valid authentication cookie if a request is made and more than half of the 
    // timeout interval has elapsed.If the cookie expires, the user must re - authenticate.Setting the SlidingExpiration property to 
    // false can improve the security of an application by limiting the time for which an authentication cookie is valid, based on the 
    // configured timeout value.
    options.SlidingExpiration = true;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this 
        // claim is generally application specific. The "iss" value is a case-sensitive string containing 
        // a StringOrURI value.  Use of this claim is OPTIONAL.
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        
        // The "iss" (issuer) claim identifies the principal that issued the JWT.The processing of this 
        // claim is generally application specific. The "iss" value is a case-sensitive string containing 
        // a StringOrURI value.Use of this claim is OPTIONAL.
        ValidateIssuer = false,

        // Usually, this is your application base URL
        ValidIssuer = "http://localhost:45092/",

        // The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal 
        // intended to process the JWT MUST identify itself with a value in the audience claim.  If the principal
        // processing the claim does not identify itself with a value in the "aud" claim when this claim is present, 
        // then the JWT MUST be rejected.  In the general case, the "aud" value is an array of case-sensitive strings, 
        // each containing a StringOrURI value.  In the special case when the JWT has one audience, the "aud" value 
        // MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience 
        // values is generally application specific. Use of this claim is OPTIONAL.
        ValidateAudience = false,

        //Here, we are creating and using JWT within the same application.
        //In this case, base URL is fine.
        //If the JWT is created using a web service, then this would be the consumer URL.
        ValidAudience = "http://localhost:45092/",

        // The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted 
        // for processing. The processing of the "exp" claim requires that the current date/time MUST be before the 
        // expiration date/time listed in the "exp" claim.
        RequireExpirationTime = true,

        // Check if token is not expired and the signing key of the issuer is valid (ValidateLifetime = true)
        ValidateLifetime = true,
    };
    // We have to hook the OnMessageReceived event in order to
    // allow the JWT authentication handler to read the access
    // token from the query string when a WebSocket or 
    // Server-Sent Events request comes in.

    // Sending the access token in the query string is required due to
    // a limitation in Browser APIs. We restrict it to only calls to the
    // SignalR hub in this code.
    // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
    // for more information about security considerations when using
    // the query string to transmit the access token.
    options.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];

            // If the request is for our hub...
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/hubs")))
            {
                // Read the token out of the query string
                context.Token = accessToken;
            }
            return Task.CompletedTask;
        }
    };
});

Appsettings.json 文件(不要在此处存储用于生产的密钥 :)

"AppSettings": {
    "Token": "secret key for jwt"
}