asp.net 如何在WebSocket请求期间验证JWT,

7y4bm7vi  于 2022-12-15  发布在  .NET
关注(0)|答案(2)|浏览(169)

我正在开发一个使用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
});
2lpgd968

2lpgd9681#

我希望这能帮助到一些人,尽管这个帖子有点老了。
我找到了答案,不是在谷歌之后,而是 Binging!我从this official code中得到了灵感。
您可以使用JwtBearerOptions的魔力,编写自己的类来非常简单地处理授权,该类(希望如此)包含您自己验证JWT所需的一切。
所以,你必须将它作为一个服务注入,并使用它来配置你的认证。

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

然后,你必须创建一个类来验证你的令牌(This is where my code was inspired)。让我们称之为支持者,因为它支持你!:

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

然后,在中间件中,只需访问请求头、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
       }

另外,AuthenticationTicket和其他任何关于auth的信息已经由框架的JwtBearerMiddleware处理过了,无论如何都会返回。
最后是客户端。我建议你使用一个实际上支持额外HTTP头的客户端库。例如,据我所知,W3C Javascript客户端不提供这个功能。
给你!感谢微软的开源代码库。

btqmn9zl

btqmn9zl2#

由于我在客户端依赖于WebSocket(),因此我采用了稍微不同的方法来解决这个问题。
因此,在客户端,我首先对用户进行身份验证以获取令牌,并将其作为子协议附加到报头:

socket = new WebSocket(connectionPath, ["client",token]);

令牌是在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;
                }
            };

然后在我的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
           // ...
    }

相关问题