簡體   English   中英

JWT 身份驗證,在 Authorize 屬性中定義的角色被忽略

[英]JWT Authentication, roles defined in Authorize attribute are ignored

在嘗試使用JWT作為默認身份驗證方案來實現基於角色的身份驗證時,我遇到了這樣一種情況,即在Authorize屬性中定義的角色被忽略,允許任何請求(帶有有效令牌)通過,即使不在那些角色,(有趣的是,在同一Authorize屬性中定義的具有自定義要求的其他策略運行良好)

閱讀傑里的文章,他提到

這是一個很好的發現:ASP.NET Core 中的 JWT 中間件知道如何解釋 JWT 有效負載中的“角色”聲明,並將適當的聲明添加到ClaimsIdentity 這使得使用 Roles 的[Authorize]屬性非常容易。

和:

真正有趣的是,當您考慮將角色傳遞給[Authorize]時,實際上會查看是否存在類型為http://schemas.microsoft.com/ws/2008/06/identity/claims/role的聲明您授權的角色的價值。 這意味着我可以簡單地將[Authorize(Roles = "Admin")]添加到任何 API 方法中,這將確保只有負載包含聲明“角色”的 JWT 才會在角色數組中包含 Admin 的值授權該 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());
}

Controller:

[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..."
}

如果需要更多詳細信息或更好的信息來回答我的問題,請告訴我。

這是關於如何使用 JWT 基於角色的身份驗證的完整工作演示:

啟動.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

事實證明,我錯誤地將我的自定義授權處理程序之一配置為接受基本IAuthorizationRequirement作為 Requirement 參數類型,而不是特定的派生 Requirement,因此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;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM