c# - IdentityServer4 - AuthorizeAttribute 不验证 JWT 令牌/自定义用户存储
问题描述
我有 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();
});
}
解决方案
问题解决了。我忘记了当我将它作为控制台应用程序启动时可以看到日志。它告诉我发行者存在问题,实际上它确实与配置类中的问题不匹配。现在它们确实匹配并且令牌工作正常。
[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Roles = "Administrator")]
角色也在起作用。
RoleClaimType = "role"
推荐阅读
- awk - 带有 IP 和端口的 GREP 和 AWK 文件
- git - 错误:RPC 失败;curl 92 HTTP/2 流 0 未完全关闭:PROTOCOL_ERROR (err 1)
- air - 原因:输入无效。ld:找不到 -lclang_rt.ios 的库
- swift - 编辑部分 UITextview
- spring - 如何指定具体的请求映射url
- python - 如何从 nGram 列表中加载计数向量器?
- ios - 如何仅在 webview 中播放视频而不是在 swift 中全屏播放
- python - 400 Error while reading data, error message: CSV table遇到太多错误,放弃。行数:29274;错误:1
- azure - 如何在构建定义工艺品中包含逻辑应用程序开发和 uat 参数 JSON 文件
- python - c++ 版本的 python 的 exec() 函数