【问题标题】:JWT Authentication, roles defined in Authorize attribute are ignoredJWT Authentication,Authorize 属性中定义的角色被忽略
【发布时间】:2021-03-21 10:53:00
【问题描述】:

在尝试使用 JWT 作为默认身份验证方案来实现 role-based-authentication 时,我遇到了Authorize 属性中定义的角色正在被忽略,允许任何请求(具有有效令牌)通过,即使不是在这些角色中,(有趣的是,在相同 Authorize 属性中定义的具有自定义要求的其他策略工作正常)

阅读jerrie's artical他提到了这一点

这是一个很好的发现:ASP.NET Core 中的 JWT 中间件知道如何解释 JWT 有效负载中的“角色”声明,并将适当的声明添加到 ClaimsIdentity。这使得在角色中使用[Authorize] 属性非常容易。

还有:

真正有趣的地方在于,当您考虑将角色传递给[Authorize] 时,实际上会查看是否存在具有您授权的角色值的http://schemas.microsoft.com/ws/2008/06/identity/claims/role 类型的声明。这意味着我可以简单地将[Authorize(Roles = "Admin")] 添加到任何 API 方法中,这将确保只有有效负载包含声明“角色”的 JWT 才会被授权使用该 API 方法。

这仍然成立吗? (这篇文章好几年了)
我做错了吗?

启动(配置服务)

public void ConfigureServices(IServiceCollection services)
{
    string defaultConnection = Configuration.GetConnectionString("Default");

    services.AddDbContext<IdentityContext>(options => options.UseSqlServer(defaultConnection).UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll));

    services.AddIdentity<AppUser, IdentityRole>()
        .AddEntityFrameworkStores<IdentityContext>()
        .AddDefaultTokenProviders();

    services.AddAuthorization(o => o.AddPolicy(Policy.IsInTenant, x => x.AddRequirements(new IsInTenantRequirement())));

    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = "somehost...",
            ValidIssuer = "somehost...",
        };
    });
}

启动(配置)

public void Configure(IApplicationBuilder app, IWebHostEnvironment envy)
{
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(x => x.MapControllers());
}

控制器:

[ApiController]
[Authorize(Roles = "some_random_string_which_is_not_registered_anywhere")] // <== any request with a valid token can access this controller
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public string Get()
    {
        return "how are you?"
    }
}

令牌服务

public class JwtService : ITokenService
{
    private readonly JwtConfig _config;
    public JwtService(IOptions<JwtConfig> config) =>  _config = config.Value;

    public string GenerateRefreshToken(int size = 32)
    {
        var randomNumber = new byte[size];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(randomNumber);
            return Convert.ToBase64String(randomNumber);
        }
    }

    public string GenerateAccessToken(IEnumerable<Claim> claims)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret));
        var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

        var tokeOptions = new JwtSecurityToken(
            issuer: _config.Issuer,
            audience: _config.Audience,
            claims: claims,
            expires: DateTime.Now.AddMinutes(int.Parse(_config.ExpirationInMinutes)),
            signingCredentials: signinCredentials
        );

        return tokenHandler.WriteToken(tokeOptions);
    }


    public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
    {
        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false, 
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.Secret)),
            ValidateLifetime = false 
        };

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

}

登录(使用令牌服务)
(目前我没有向令牌添加任何角色,但用户可以完全访问由特定角色保护的资源。)

[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> Post(LoginDTO model)
{
    if (!ModelState.IsValid) return BadRequest("errors.invalidParams");

    var user = await _userManager.FindByEmailAsync(model.Email);
    if (user == null)
    {
        return Unauthorized("errors.loginFailure");
    }

    var result = await _signInManager.PasswordSignInAsync(user?.UserName, model.Password, model.RememberMe, false);


    if (result.Succeeded)
    {
        var claims = new List<Claim>
        {
            new Claim(AppClaim.TenantId, user.TenantId.ToString()),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        };

        claims.AddRange(await _authOperations.GetUserRolesAsClaims(user));
        claims.AddRange(await _authOperations.GetAllUserClaims(user));

        var accessToken = _tokenService.GenerateAccessToken(claims);
        var refreshToken = _tokenService.GenerateRefreshToken();
        user.RefreshToken = refreshToken;
        user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);

        await _ctx.SaveChangesAsync();

        return Ok(new TokenExchangeDTO
        {
            AccessToken = accessToken,
            RefreshToken = refreshToken
        });
    }

appsettings.json

"JwtConfig": {
  "Secret": "secret...",
  "ExpirationInMinutes": 1440,
  "Issuer": "somehost...",
  "Audience": "somehost..."
}

如果需要更多详细信息或更好的信息来回答我的问题,请告诉我。

【问题讨论】:

  • 你是如何生成令牌的?

标签: c# asp.net-core jwt authorization asp.net-identity


【解决方案1】:

事实证明,我错误地将我的一个自定义授权处理程序配置为接受基本 IAuthorizationRequirement 作为需求参数类型,而不是特定的派生需求,因此基本上任何需求都调用了 context.Succeed(requirement)将其标记为成功。

原代码:

public class IsInTenantRequirement : IAuthorizationRequirement { }

public class IsInTenantAuthorizationHandler : AuthorizationHandler<IAuthorizationRequirement>
{
    private readonly RouteData _routeData;

    public IsInTenantAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    {
        _routeData = httpContextAccessor.HttpContext.GetRouteData();
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IAuthorizationRequirement requirement)
    {
        var tenantIdFromRequest = _routeData.Values["tenantId"]?.ToString();
        var tenantId = context.User.FindFirstValue(AppClaim.TenantId);

        if (tenantIdFromRequest == tenantId)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

更新后的代码:

public class IsInTenantRequirement : IAuthorizationRequirement { }

public class IsInTenantAuthorizationHandler : AuthorizationHandler<IsInTenantRequirement>
{
    private readonly RouteData _routeData;

    public IsInTenantAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    {
        _routeData = httpContextAccessor.HttpContext.GetRouteData();
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsInTenantRequirement requirement)
    {
        var tenantIdFromRequest = _routeData.Values["tenantId"]?.ToString();
        var tenantId = context.User.FindFirstValue(AppClaim.TenantId);

        if (tenantIdFromRequest == tenantId)
        {
            context.Succeed(requirement);
        }
        context.Succeed(requirement);


        return Task.CompletedTask;
    }
}

【讨论】:

    【解决方案2】:

    以下是有关如何使用基于 JWT 角色的身份验证的完整演示:

    Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = Configuration["Jwt:JwtIssuer"],
                    ValidAudience = Configuration["Jwt:JwtIssuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:JwtKey"])),
                    ValidateIssuer = true, 
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                };
            });       
    }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseHttpsRedirection();
        app.UseRouting();
    
        app.UseAuthentication(); 
            
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapControllers();
        });
    }
    

    在 appSettings.json 中存储 IssuerAudienceSigningKey

    "jwt": {
        "JwtKey": "YourJwtKey",
        "JwtIssuer": "YourJwtIssuer"
    }
    

    生成令牌:

    [Route("api/[Controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private IConfiguration _config;
        public ValuesController(IConfiguration config)
        {
            _config = config;
        }
        [Route("GenerateToken")]
        public async Task<IActionResult> GenerateToken()
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Role, "Admin")
            };
            var token = new JwtSecurityToken(_config["Jwt:JwtIssuer"],
                                             _config["Jwt:JwtIssuer"],
                                             claims: claims,
                                             expires: DateTime.Now.AddDays(5),
                                             signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:JwtKey"])),
                                                 SecurityAlgorithms.HmacSha256));
            var data = new JwtSecurityTokenHandler().WriteToken(token);
            return Ok(new { data });                   
        }
    }
    

    测试方法:

    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        [Authorize(Roles = "admin")]
        [HttpGet]
        public async Task<IActionResult> Get()
        {
    
            return Ok();
        }
        [Authorize(Roles = "Admin")]
        [HttpGet("GetAdmin")]
        public async Task<IActionResult> GetAdmin()
        {
    
            return Ok();
        }
    }
    

    结果:

    参考:

    https://stackoverflow.com/a/61403262/11398810

    【讨论】:

    • 谢谢你的回答,看来我做的一切都是正确的,我已经添加了你需要的部分
    • 查看您的示例项目1drv.ms/u/s!AkZwXaPsV4CxgkbR_a95JsHjbb6s?e=zUWTHS 似乎您正在使用需要角色的策略,而不是使用内置角色功能
    • 嗨@RafiHenig,不,你不需要检查我以前的项目。我现在所做的使用内置角色功能。实际上,我很高兴你已经解决了你的问题。跨度>
    猜你喜欢
    • 2021-11-24
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    • 2016-08-15
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多