首页 > 解决方案 > 使用 JWT 在 Web API 的每个请求中设置声明主体

问题描述

我有 SPA 作为 React 中的前端应用程序和 APP.NET Web API 中的后端应用程序。我有一个对用户进行身份验证的身份管理,因此我们从 React 应用程序直接对身份管理系统进行 API 调用,以传递用户凭据。一旦通过身份验证,我们就会通过调用后端 API 来显示用户的基本详细信息。我有一个具有身份验证过滤器属性的基本控制器,因此我们将设置 httpusercontext 和主体对象。所以首先我检查如果没有设置 httpcontext.currrent.user.name

  1. 从身份管理返回的请求标头中获取用户名
  2. 从数据库中按用户名获取用户详细信息并设置声明
  3. 检查标头是否有 JWT 令牌,如果有,则验证它,如果没有,则将令牌添加到标头
  4. 设置httpcontext

代码看起来像

if ((HttpContext.Current != null && HttpContext.Current.User != null && HttpContext.Current.User.Identity != null && string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)))
{
    IEnumerable<string> tokenHeaders;
    //get the value of username from Idenity Access management
    if (request.Headers.TryGetValues("username", out tokenHeaders))
    {
        userId = tokenHeaders.FirstOrDefault();
        if (userId != null)
        {
            ClientModel clientModel = new ClientModel();
            //get all the details to construct claims from database
            clientModel = _processor.GetDetailsByUsername(userId);
            
            var claims = new List<Claim>
            {
                new Claim("FirstName", clientModel.FirstName),
                new Claim("LastName", clientModel.LastName),
                new Claim(ClaimTypes.Name, clientModel.UserName),
                new Claim("ClientId", clientModel.UserId.ToString()),
                new Claim(ClaimTypes.Email, clientModel.EmailId)
            };
                        
            //check if JwtToken exists and if not create access and refresh token
            if (!request.Headers.Contains("JwtToken"))
            {
                jwtTokenViewModel = new JwtTokenViewModel();
                jwtTokenViewModel.AccessToken = _tokenService.GenerateAccessToken(claims);
                jwtTokenViewModel.RefreshToken = _tokenService.GenerateRefreshToken();
                //TODO: persist refresh token
                context.ActionContext.Request.Properties.Add("JwtTokenProperty", jwtTokenViewModel);

            }
            else
            {
                //validate the token
                IEnumerable<string> headerValues = request.Headers.GetValues("JwtToken");
                var accessToken = headerValues.FirstOrDefault();
                var principal = _tokenService.GetPrincipalFromExpiredToken(accessToken);
                if (principal == null)
                {
                    ShowAuthenticationError(context);
                }
            }
            ClaimsIdentity identity = new ClaimsIdentity(claims);
            Thread.CurrentPrincipal = new ClaimsPrincipal(new[] { identity });
            System.Web.HttpContext.Current.User = new ClaimsPrincipal(new[] { identity });
        }
    }
}

它有效,但据我了解 httpcontext 对该请求有效,因为 httpcontext.current.user.name 对于每个请求都是空的。这是使用个人帐户而不是 Windows 身份验证。该方法的一个问题是对于我们调用数据库并设置主体对象的每个请求。所以我想知道使用令牌来设置声明是否是一个更好的主意。所以我在考虑过滤器中的以下步骤

如果标记出现在标头中

  1. 验证令牌并从令牌中获取声明
  2. 设置声明并设置 httpcontext 用户

如果标头中没有标记

  1. 从请求头中获取用户名
  2. 从数据库中获取用户详细信息并设置声明
  3. 根据声明生成令牌并将其添加到标头

所以代码看起来像

if(!request.Headers.Contains("JwtToken"))
{
    IEnumerable<string> tokenHeaders;
    //get the value of username from Idenity Access management
    if (request.Headers.TryGetValues("username", out tokenHeaders))
    {
        userId = tokenHeaders.FirstOrDefault();
        if (userId != null)
        {
            ClientModel clientModel = new ClientModel();
            //get all the details to construct claims from database
            clientModel = _processor.GetDetailsByUsername(userId);
            
            var claims = new List<Claim>
            {
                new Claim("FirstName", clientModel.FirstName),
                new Claim("LastName", clientModel.LastName),
                new Claim(ClaimTypes.Name, clientModel.UserName),
                new Claim("ClientId", clientModel.UserId.ToString()),
                new Claim(ClaimTypes.Email, clientModel.EmailId)
            };
                        
            //check if JwtToken exists and if not create access and refresh token
            if (!request.Headers.Contains("JwtToken"))
            {
                jwtTokenViewModel = new JwtTokenViewModel();
                jwtTokenViewModel.AccessToken = _tokenService.GenerateAccessToken(claims);
                jwtTokenViewModel.RefreshToken = _tokenService.GenerateRefreshToken();
                //TODO: persist refresh token
                context.ActionContext.Request.Properties.Add("JwtTokenProperty", jwtTokenViewModel);

            }
        }
    }
}
else
{
    //validate the token
    IEnumerable<string> headerValues = request.Headers.GetValues("JwtToken");
    var accessToken = headerValues.FirstOrDefault();
    var principal = _tokenService.GetPrincipalFromExpiredToken(accessToken);
    if (principal == null)
    {
        ShowAuthenticationError(context);
    }
}
ClaimsIdentity identity = new ClaimsIdentity(claims);
Thread.CurrentPrincipal = new ClaimsPrincipal(new[] { identity });
System.Web.HttpContext.Current.User = new ClaimsPrincipal(new[] { identity });

通过上述方法,我们可以避免每个请求都调用数据库。但是我们在令牌中包含了用户 ID(我认为无论如何我都需要它来验证)。请让我知道什么是更好的方法以及为什么以及是否还有其他更好的方法。

标签: c#asp.netapiwebjwt

解决方案


推荐阅读