防止用户使用JWT令牌进行多个会话 [英] Prevent users to have multiple sessions with JWT Tokens

查看:106
本文介绍了防止用户使用JWT令牌进行多个会话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个在ASP.NET Core中使用JWT承载身份验证的应用程序.我需要防止用户同时打开多个会话.我想知道是否可以使用Microsoft.AspNetCore.Authentication.JwtBearer中间件列出用户的所有令牌,然后验证是否为该用户发行了其他令牌,以使传入的身份验证请求无效.

I am building an application which uses JWT bearer authentication in ASP.NET Core. I need to prevent users to have multiple sessions open at the same time. I am wondering if there is way using Microsoft.AspNetCore.Authentication.JwtBearer middleware to list out all the tokens of an user and then verify if there are other tokens issued for that user in order to invalidate the incoming authentication request.

如果可以在服务器上验证声明,那么我想为了做到这一点,服务器需要记录这些声明以及拥有它们的用户.对吧?

If the claims are able to be validated on the server, I guess that in order to do that, the server has a record of those claims and the user who owns them. Right?

任何想法我该如何实现?

Any ideas How Can I achieve this?

推荐答案

我已经实现了我的目标,将用户登录时的时间戳保存在db中,将该时间戳添加到令牌的有效负载中,然后添加一个额外的安全性以针对数据库验证JWT,如果时间戳不匹配,则返回401.如果有人需要,这是使用.net Core 2.0实现的代码.

I have achieved my goal, saving in the db the timestamp when the user login, adding that timestamp in the payload of the token and then adding an additional layer of security to validate the JWT against the db, returning 401 if the timestamp doesn't match. This is the code implemented with .net Core 2.0 if someone need it.

控制器:

    [HttpPost]
    [Route("authenticate")]
    public async Task<IActionResult> AuthenticateAsync([FromBody] UserModel user)
    {
        try
        {
            .......

            if (userSecurityKey != null)
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                       // This claim allows us to store information and use it without accessing the db
                        new Claim("userSecurityKey", userDeserialized.SecurityKey.ToString()),
                        new Claim("timeStamp",timeStamp),
                        new Claim("verificationKey",userDeserialized.VerificationKey.ToString()),
                        new Claim("userName",userDeserialized.UserName)

                    }),
                    Expires = DateTime.UtcNow.AddDays(7),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
                        SecurityAlgorithms.HmacSha256Signature)
                };
                var token = tokenHandler.CreateToken(tokenDescriptor);
                var tokenString = tokenHandler.WriteToken(token);

               // Updates timestamp for the user if there is one
                VerificationPortalTimeStamps userTimeStamp = await _context.VerificationPortalTimeStamps.AsNoTracking().FirstOrDefaultAsync(e => e.UserName == userDeserialized.UserName);

                if (userTimeStamp != null)
                {
                    userTimeStamp.TimeStamp = timeStamp;
                    _context.Entry(userTimeStamp).State = EntityState.Modified;
                   await _context.SaveChangesAsync();
                }
                else
                {
                    _context.VerificationPortalTimeStamps.Add(new VerificationPortalTimeStamps { TimeStamp = timeStamp, UserName = userDeserialized.UserName });
                    await _context.SaveChangesAsync();
                }


                // return basic user info (without password) and token to store client side                   
                return Json(new
                {
                    userName = userDeserialized.UserName,
                    userSecurityKey = userDeserialized.SecurityKey,
                    token = tokenString
                });
            }

            return Unauthorized();

        }
        catch (Exception)
        {
            return Unauthorized();
        }
    }

然后,使用.Net Core 2.0配置JWT承载身份验证

Then, to configure the JWT Bearer Authentication with .Net Core 2.0

Startup.cs:

Startup.cs:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider provider)
    {
        .................     

        app.UseAuthentication();

        app.UseMvc();

        ...........

    }

要配置JWT承载身份验证,请执行以下操作:

To configure the JWT Bearer authentication:

public IServiceProvider ConfigureServices(IServiceCollection services)
    {

        ............


        var key = Configuration["AppSettings:Secret"];

        byte[] keyAsBytes = Encoding.ASCII.GetBytes(key);

        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
            .AddJwtBearer(o =>
            {
                o.RequireHttpsMetadata = false;
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(keyAsBytes),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true

                };

                o.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (Configuration["AppSettings:IsGodMode"] != "true")
                            context.Response.StatusCode = 401;


                        return Task.FromResult<object>(0);
                    }
                };
                o.SecurityTokenValidators.Clear();
                o.SecurityTokenValidators.Add(new MyTokenHandler());
            });

        services.AddMvc()                
            .AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });


        var provider = services.BuildServiceProvider();


        return provider;
    }

然后,我们在控制器中实现自定义验证,如下所示:

Then, we implement the custom validation in the controller as follows:

    [Authorize]
    [HttpGet]
    [Route("getcandidate")]
    public async Task<IActionResult> GetCandidateAsync()
    {

        try
        {
            .....

            //Get user from the claim
            string userName = User.FindFirst("UserName").Value;

            //Get timestamp from the db for the user
            var currentUserTimeStamp = _context.VerificationPortalTimeStamps.AsNoTracking().FirstOrDefault(e => e.UserName == userName).TimeStamp;

           // Compare timestamp from the claim against timestamp from the db
            if (User.FindFirst("timeStamp").Value != currentUserTimeStamp)
            {
                return NotFound();
            }

            ...........

        }
        catch (Exception)
        {
            return NotFound();
        }
    }

这篇关于防止用户使用JWT令牌进行多个会话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆