【问题标题】:How to validate JWT during websocket request. .net core如何在 websocket 请求期间验证 JWT。 .net 核心
【发布时间】:2017-06-06 17:12:58
【问题描述】:

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

【问题讨论】:

    标签: c# asp.net asp.net-web-api asp.net-core asp.net-core-mvc


    【解决方案1】:

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

    我找到了答案,不是在谷歌搜索后,而是 Binging !我的灵感来自this official code

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

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

    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 和任何其他有关身份验证的信息已由框架的 JwtBearerMiddleware 处理,并且无论如何都会返回。

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

    你来了!感谢 Microsoft 的开源代码库。

    【讨论】:

    • 谢谢老兄。如果以后再出现,我会用这个!
    【解决方案2】:

    我的解决方法略有不同,因为我依赖于客户端的 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
               // ...
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-06
      • 2020-04-10
      • 2019-08-02
      • 2021-03-24
      • 1970-01-01
      • 2019-06-23
      • 2017-06-24
      • 2017-05-13
      相关资源
      最近更新 更多