如何在 websocket 请求期间验证 JWT。.net核心

Joh*_*ith 5 c# asp.net asp.net-web-api asp.net-core-mvc asp.net-core

我正在开发一个使用 JWT 身份验证和 websockets 的小型 .net 核心应用程序。

我已成功实现为标准 Web api 控制器生成和验证令牌。但是,我也想验证WebSocket请求的令牌,这当然不适用于[Authorize]属性。

我已经像这样设置了我的中间件管道:

app.UseWebSockets();
app.Use(async (http, next) => {
      if (http.WebSockets.IsWebSocketRequest == false) {
          await next();
          return;
      }
      /// Handle websocket request here. How to check if token is valid?
});

// secretKey contains a secret passphrase only your server knows
var secretKey = .....;
var signKey = new SigningCredentials (
    new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)),
    SecurityAlgorithms.HmacSha256
);

var tokenValidationParameters = new TokenValidationParameters {
    ValidateIssuer = false,
    ValidateAudience = false,

    // The signing key must match!
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = signKey.Key,

    // Validate the token expiry
    ValidateLifetime = true,

    // If you want to allow a certain amount of clock drift, set that here:
    ClockSkew = TimeSpan.FromMinutes(1),
};


app.UseJwtBearerAuthentication(new JwtBearerOptions {
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = tokenValidationParameters
});
Run Code Online (Sandbox Code Playgroud)

Chr*_* J. 5

我希望这可以帮助某人,即使帖子有点旧。

我找到了答案,不是在谷歌搜索之后,而是在Binging 之后!我从这个官方代码中获得了灵感。

您可以使用 JwtBearerOptions 的魔力编写自己的类来非常简单地处理授权。这个类(希望)包含您自己验证 JWT 所需的一切。

因此,您必须将其作为服务注入,并使用它来配置您的身份验证。类似的东西在你的Startup.ConfigureServices

this.JwtOptions = new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            TokenValidationParameters = yourTokenValidationParameters
        };
services.AddSingleton<JwtBearerOptions>(this.JwtOptions);
Run Code Online (Sandbox Code Playgroud)

然后,您必须创建一个类来验证您的令牌(这是我的代码的灵感来源)。让我们称其为支持者,因为他支持您!:

public class JwtBearerBacker
{
    public JwtBearerOptions Options { get; private set; }

    public JwtBearerBacker(JwtBearerOptions options)
    {
        this.Options = options;
    }

    public bool IsJwtValid(string token)
    {
        List<Exception> validationFailures = null;
        SecurityToken validatedToken;
        foreach (var validator in Options.SecurityTokenValidators)
        {
            if (validator.CanReadToken(token))
            {
                ClaimsPrincipal principal;
                try
                {
                    principal = validator.ValidateToken(token, Options.TokenValidationParameters, out validatedToken);
                }
                catch (Exception ex)
                {
                    // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
                    if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
                        && ex is SecurityTokenSignatureKeyNotFoundException)
                    {
                        Options.ConfigurationManager.RequestRefresh();
                    }

                    if (validationFailures == null)
                        validationFailures = new List<Exception>(1);
                    validationFailures.Add(ex);
                    continue;
                }
                return true;
            }
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在您的中间件中,只需访问请求标头、JwtOptions依赖项并调用Backer

 protected string ObtainAppTokenFromHeader(string authHeader)
    {
        if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.Contains(" "))
            return null;
        string[] authSchemeAndJwt = authHeader.Split(' ');
        string authScheme = authSchemeAndJwt[0];
        if (authScheme != "Bearer")
            return null;
        string jwt = authSchemeAndJwt[1];
        return jwt;
    }

    protected async Task<bool> AuthorizeUserFromHttpContext(HttpContext context)
    {
        var jwtBearerOptions = context.RequestServices.GetRequiredService<JwtBearerOptions>() as JwtBearerOptions;
        string jwt = this.ObtainAppTokenFromHeader(context.Request.Headers["Authorization"]);
        if (jwt == null)
            return false;
        var jwtBacker = new JwtBearerBacker(jwtBearerOptions);
        return jwtBacker.IsJwtValid(jwt);
    }

    public async Task Invoke(HttpContext context)
    {
        if (!context.WebSockets.IsWebSocketRequest)
            return;
        if (!await this.AuthorizeUserFromHttpContext(context))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("The door is locked, dude. You're not authorized !");
            return;
        }
//... Whatever else you're doing in your middleware
       }
Run Code Online (Sandbox Code Playgroud)

此外,AuthenticationTicket框架的 已经处理了有关身份验证的和任何其他信息JwtBearerMiddleware,并且无论如何都会返回。

最后是客户端。我建议您使用实际支持附加 HTTP 标头的客户端库。例如,据我所知,W3C Javascript 客户端不提供此功能。

这个给你!感谢微软的开源代码库。


Art*_*ior 2

我的解决方法略有不同,因为我取决于WebSocket()客户端。

因此,在客户端,我首先对用户进行身份验证以获取令牌并将其作为子协议附加到标头:

socket = new WebSocket(connectionPath, ["client",token]);
Run Code Online (Sandbox Code Playgroud)

令牌在密钥下的请求标头中发送sec-websocket-protocol。因此,在身份验证开始之前,我提取令牌并将其附加到上下文中。

        .AddJwtBearer(x =>
        {
          // ....

            x.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    if (context.Request.Headers.ContainsKey("sec-websocket-protocol") && context.HttpContext.WebSockets.IsWebSocketRequest)
                    {
                        var token = context.Request.Headers["sec-websocket-protocol"].ToString();
                        // token arrives as string = "client, xxxxxxxxxxxxxxxxxxxxx"
                        context.Token = token.Substring(token.IndexOf(',') + 1).Trim();
                        context.Request.Headers["sec-websocket-protocol"] = "client";
                    }
                    return Task.CompletedTask;
                }
            };
Run Code Online (Sandbox Code Playgroud)

然后在我的 WebSocket 控制器上我只需粘贴[Authorize]属性:

    [Authorize]
    [Route("api/[controller]")]
    public class WSController : Controller
    {           
        [HttpGet]
        public async Task Get()
        {
            var context = ControllerContext.HttpContext;    
            WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync("client"); // it's important to make sure the response returns the same subprotocol
           // ...
    }
Run Code Online (Sandbox Code Playgroud)