IdentityServer4 - AuthorizeAttribute 不验证 JWT 令牌/自定义用户存储

Posted

技术标签:

【中文标题】IdentityServer4 - AuthorizeAttribute 不验证 JWT 令牌/自定义用户存储【英文标题】:IdentityServer4 - AuthorizeAttribute doesn't validate the JWT token / Custom user store 【发布时间】:2021-10-07 06:47:04 【问题描述】:

我有 Angular 12 前端和 ASP.NET Core 5 后端。后端使用自定义用户存储实现了 IdentityServer4,因为以后可以轻松地将其替换为 Active Directory。

问题是AuthorizeAttribute ([Authorize]) 不起作用。它只是一直显示 401 未经授权。我认为services.AddAuthentication 有问题,因为它负责令牌的验证。

我还希望能够使用角色[Authorize(Roles = Role.Administrator,Role.DepartmentAdministrator)]。我相信如果令牌开始工作 RoleClaimType = "role" 会使其工作,但我不能确定,直到我先修复令牌验证。

片段

public static class InfrastructureServicesExtensions

    public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
    
        ...
        
        services.AddIdentityServer(options =>
        
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;

            options.EmitStaticAudienceClaim = true;
        )
            .AddDeveloperSigningCredential()
            .AddInMemoryIdentityResources(Configuration.GetIdentityResources())
            .AddInMemoryApiScopes(Configuration.GetApiScopes())
            .AddInMemoryApiResources(Configuration.GetApiResources(configuration))
            .AddInMemoryClients(Configuration.GetClients(configuration))
            .AddCustomUserStore();

        services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
        //.AddJwtBearer(options =>
        //
        //    options.RequireHttpsMetadata = false;
        //    options.SaveToken = true;
        //    options.TokenValidationParameters = new TokenValidationParameters
        //    
        //        ValidateIssuerSigningKey = true,
        //        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["AuthConfiguration:ClientSecret"])),
        //        ValidateIssuer = false,
        //        ValidateAudience = false
        //    ;
        //);
            .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, jwtOptions =>
            
                jwtOptions.Authority = "http://localhost:5000";
                jwtOptions.RequireHttpsMetadata = false;
            ,
            referenceOptions =>
            
                referenceOptions.Authority = "http://localhost:5000";

                referenceOptions.RoleClaimType = "role";

                referenceOptions.ClientId = configuration["AuthConfiguration:ClientId"];
                referenceOptions.ClientSecret = configuration["AuthConfiguration:ClientSecret"];
            );

        return services;
    


[Authorize]
public class RoomsController : ApiControllerBase

    [HttpGet]
    public async Task<ActionResult<IList<RoomDto>>> GetRooms()
    
        var result = await Mediator.Send(new GetRoomsQuery()).ConfigureAwait(false);

        return Ok(result);
    

    [HttpPost]
    public async Task<ActionResult<int>> Create(CreateRoomCommand command)
    
        return await Mediator.Send(command).ConfigureAwait(false);
    

    [HttpPut("id:int")]
    public async Task<ActionResult> Update(int id, UpdateRoomCommand command)
    
        if (id != command.Id)
        
            return BadRequest();
        

        await Mediator.Send(command).ConfigureAwait(false);

        return NoContent();
    

    [HttpDelete("id:int")]
    public async Task<ActionResult> Delete(int id)
    
        await Mediator.Send(new DeleteRoomCommand  Id = id ).ConfigureAwait(false);

        return NoContent();
    


public static class CustomIdentityServerBuilderExtensions

    public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
    
        builder.Services.AddSingleton<IUserRepository, UserRepository>();

        builder
            .AddProfileService<CustomProfileService>()
            .AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>();

        return builder;
    


public class CustomProfileService : IProfileService

    private readonly IUserRepository _userRepository;

    public CustomProfileService(IUserRepository userRepository)
    
        _userRepository = userRepository;
    

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    
        var sub = context.Subject.GetSubjectId();
        var user = _userRepository.FindById(sub);

        var claims = new List<Claim>
        
            new("username", user.Username),
            new("email", user.Email),
            new("role", user.Role.ToDescriptionString())
        ;

        context.IssuedClaims = claims;

        return Task.CompletedTask;
    

    public Task IsActiveAsync(IsActiveContext context)
    
        var sub = context.Subject.GetSubjectId();
        var user = _userRepository.FindById(sub);

        context.IsActive = user != null;

        return Task.CompletedTask;
    


public class UserRepository : IUserRepository

    private readonly List<User> _users = new()
    
        new User
        
            Id = "1",
            Username = "admin",
            Password = "123456",
            Email = "admin@uni-ruse.bg",
            Role = Role.Administrator
        ,
        new User
        
            Id = "2",
            Username = "katadmin",
            Password = "123456",
            Email = "katadmin@uni-ruse.bg",
            Role = Role.DepartmentAdministrator
        ,
        new User
        
            Id = "3",
            Username = "user",
            Password = "123456",
            Email = "user@uni-ruse.bg",
            Role = Role.User
        
    ;

    public bool ValidateCredentials(string username, string password)
    
        var user = FindByUsername(username);
        return user != null && user.Password.Equals(password);
    

    public User FindById(string id)
    
        return _users.FirstOrDefault(x => x.Id == id);
    

    public User FindByUsername(string username)
    
        return _users.FirstOrDefault(x => x.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
    


public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator

    private readonly IUserRepository _userRepository;

    public CustomResourceOwnerPasswordValidator(IUserRepository userRepository)
    
        _userRepository = userRepository;
    

    public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    
        if (_userRepository.ValidateCredentials(context.UserName, context.Password))
        
            var user = _userRepository.FindByUsername(context.UserName);
            context.Result = new GrantValidationResult(user.Id, OidcConstants.AuthenticationMethods.Password);
        

        return Task.CompletedTask;
    


public enum Role

    [Description("Администратор")]
    Administrator,

    [Description("Катедрен администратор")]
    DepartmentAdministrator,

    [Description("Потребител")]
    User


public class User

    public string Id  get; set; 
    public string Email  get; set; 
    public string Username  get; set; 
    public string Password  get; set; 
    public Role Role  get; set; 


public static class Configuration

    public static IEnumerable<IdentityResource> GetIdentityResources() =>
        new List<IdentityResource>
            
            new IdentityResources.OpenId(),
            new IdentityResources.Profile()
            ;

    public static IEnumerable<ApiScope> GetApiScopes() =>
        new List<ApiScope>
        
            new("assapi", "Academic Schedule API")
        ;

    public static IEnumerable<ApiResource> GetApiResources(IConfiguration configuration) =>
        new List<ApiResource>
        
            new("assapi", "Academic Schedule API")
            
                ApiSecrets = new List<Secret>
                
                    new(configuration["AuthConfiguration:ClientSecret"].Sha256())
                ,
                Scopes =
                
                    "assapi"
                
            
        ;

    public static IEnumerable<Client> GetClients(IConfiguration configuration) =>
        new List<Client>
        
            new()
            
                ClientName = configuration["AuthConfiguration:ClientName"],
                ClientId = configuration["AuthConfiguration:ClientId"],
                ClientSecrets =  new Secret(configuration["AuthConfiguration:ClientSecret"].Sha256()) ,

                AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                AccessTokenType = AccessTokenType.Jwt,
                AllowOfflineAccess = true,

                AccessTokenLifetime = 120,
                IdentityTokenLifetime = 120,
                UpdateAccessTokenClaimsOnRefresh = true,
                SlidingRefreshTokenLifetime = 300,
                RefreshTokenExpiration = TokenExpiration.Absolute,
                RefreshTokenUsage = TokenUsage.OneTimeOnly,
                AlwaysSendClientClaims = true,

                AllowedScopes =
                
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "assapi"
                
            
        ;

public void Configure(IApplicationBuilder app)

    if (Environment.IsDevelopment())
    
        app.UseDeveloperExceptionPage();
    

    app.UseCors("CorsPolicy");

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Academic Schedule API V1");
    );

    app.UseRouting();

    app.UseAuthentication();
    app.UseIdentityServer();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    
        endpoints.MapDefaultControllerRoute();
    );

【问题讨论】:

【参考方案1】:

问题解决了。我忘记了当我将它作为控制台应用程序启动时可以看到日志。它告诉我发行者存在问题,实际上它确实与配置类中的问题不匹配。现在它们确实匹配并且令牌工作正常。

[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Roles = "Administrator")]

角色也在起作用。

RoleClaimType = "role"

【讨论】:

以上是关于IdentityServer4 - AuthorizeAttribute 不验证 JWT 令牌/自定义用户存储的主要内容,如果未能解决你的问题,请参考以下文章

IdentityServer4源码解析_4_令牌发放接口

IdentityServer4源码解析_5_查询用户信息接口

IdentityServer4实战 - 与API单项目整合

IdentityServer4 综合应用实战系列 登录

IdentityServer4 访问令牌更新

IdentityServer4 综合应用实战系列 登录