首页 > 解决方案 > 当方法没有在 Asp.NET Core 3.0 API 控制器中使用 Authorize 修饰时,User.Identity.Name 为空,JWT

问题描述

我在 .net core 3.1 中有一个 Web Api 项目,并且添加了 JwT 身份验证。身份验证和授权工作得很好,但我需要在每个请求中获取 UserId。当方法用 Authorize 属性修饰时,这很好用。

[HttpGet]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public IEnumerable<WeatherForecast> Get()
    {
        string user = User.Identity.Name; //Get a value
        //Do something
    }

但是我有一些不需要身份验证的方法,但是如果经过身份验证的用户发出请求,我想获取 userId,但在这种情况下,user.Identity.Name 始终为空。

[HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        string user = User.Identity.Name; //null
        //Do somwthing
    }

我在 statur 文件中的配置是:

private void ConfigureJwt(IServiceCollection services)
    {
        //Add Auth scheme
        services.AddAuthorization(options =>
        {
            var defaultAuthorizationPolicyBuilder = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
            defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
            options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
        });

        AuthSettings authSettings = Configuration.GetSection("AuthSettings").Get<AuthSettings>();
        JwtIssuerOptions jwtIssuerOptions = Configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();

        services.AddAuthentication(opt =>
        {
            opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,

                ValidIssuer = jwtIssuerOptions.Issuer,
                ValidAudience = jwtIssuerOptions.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey))
            };
            options.Events = new JwtBearerEvents
            {
                OnTokenValidated = context =>
                {
                    //When method is no decorated with Authorize, it not working
                    var userId = int.Parse(context.Principal.Identity.Name);
                    return System.Threading.Tasks.Task.CompletedTask;
                }
            };
        });

        services.AddTransient<ITokenService, TokenService>(x =>
        {
            return new TokenService(Configuration);
        });
    } 

令牌服务类:

public class TokenService : ITokenService
{
    IConfiguration configuration = null;
    AuthSettings authSettings = null;
    public TokenService(IConfiguration _configuration)
    {
        configuration = _configuration;
        authSettings = configuration.GetSection("AuthSettings").Get<AuthSettings>();
    }

    public string GenerateAccessToken(IEnumerable<Claim> claims, ref JwtIssuerOptions jwtIssuerOptions)
    {
        //var authSettings = configuration.GetSection(nameof(AuthSettings));
        //var authSettings = configuration.GetSection("EmailSettings").Get<AuthSettings>();
        jwtIssuerOptions = configuration.GetSection("JwtIssuerOptions").Get<JwtIssuerOptions>();

        var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey));
        var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

        var tokeOptions = new JwtSecurityToken (
            issuer: jwtIssuerOptions.Issuer,
            audience: jwtIssuerOptions.Audience,
            claims: claims,
            expires: jwtIssuerOptions.Expiration,
            //expires: DateTime.Now.AddMinutes(5),
            signingCredentials: signinCredentials
        );

        var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
        return tokenString;
    }

    public string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }
    public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
    {
        TokenValidationParameters tokenValidationParameters = GetValidationParameters();

        var tokenHandler = new JwtSecurityTokenHandler();
        SecurityToken securityToken;
        var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
        var jwtSecurityToken = securityToken as JwtSecurityToken;
        if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
            throw new SecurityTokenException("Invalid token");

        return principal;
    }

    private TokenValidationParameters GetValidationParameters()
    {
        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey)),
            ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
        };
        return tokenValidationParameters;
    }
}

授权控制器

    [HttpPost, Route("login")]
    public async Task<IActionResult> Login([FromBody] LoginModel loginModel)
    {
        if (loginModel == null)
            return BadRequest("Invalid client request");

        var sessionInfo = await userBo.LoginUser(loginModel);
        if (sessionInfo == null)
            return Unauthorized();

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, sessionInfo.User.BusinessEntityId.ToString()),
            new Claim(ClaimTypes.Role, sessionInfo.User.RoleCode)
        };

        JwtIssuerOptions tokeOptions = null;
        var accessToken = tokenService.GenerateAccessToken(claims, ref tokeOptions);
        var refreshToken = tokenService.GenerateRefreshToken();

        await tokenBo.SaveToken(
            new Token()
            {
                BusinessEntityId = sessionInfo.Person.BusinessEntityId,
                RefreshToken = refreshToken,
                RefreshTokenExpiryTime = tokeOptions.Expiration
            }
        );

        sessionInfo.TokenInfo = new TokenInfo()
        {
            AccessToken = accessToken,
            RefreshToken = refreshToken
        };
        return Ok(sessionInfo);
    }
}

谢谢您的帮助!

标签: asp.net-core.net-corejwt

解决方案


据我所知,如果控制器不需要授权,它不会将用户信息添加到管道声明中,因此用户名始终为空。

为了解决这个问题,我建议您可以尝试添加一个自定义中间件来检查请求是否包含 Authorization 标头。如果它包含您可以获取用户名并将其添加到 http 上下文项中。

然后你可以直接在 api 控制器中获取用户名,而不是从 User.Identity.Name 中获取。

更多细节,您可以参考以下代码:

将以下中间件添加到 startup.cs 配置方法中:

        app.Use(async (context, next) =>
        {
            // you could get from token or get from session. 
            string token = context.Request.Headers["Authorization"];
            if (!string.IsNullOrEmpty(token))
            {
                var tok = token.Replace("Bearer ", "");
                var jwttoken = new JwtSecurityTokenHandler().ReadJwtToken(tok);

                 var jti = jwttoken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
                context.Items.Add("Username", jti);
            }
    
            await next();

        });

控制器获取用户名:

        object value;
        ControllerContext.HttpContext.Items.TryGetValue("Username", out value);

        var username = value.ToString();

结果:

在此处输入图像描述


推荐阅读