用于授权和身份验证的多个 JWT 承载
Multiple JWT bearers for authorization and authentication
我有一个用于 SSO 的 .NET Core IdentityServer (IS),我想用它来验证我的 .NET Core(后端)-Angular(客户端)应用程序。我想在后端有一个 EF ApplicationUser,并通过自定义 backend-generated JWT 令牌在后端有一个基于声明的授权,这也适用于客户端授权。
在后端,我创建了一个中间件来检查所有请求的 "Authorization" header。如果 header 包含 IS 生成的令牌,我想将其交换为包含必要声明的自定义(后端)生成的令牌。客户端然后将此 header 用于后续请求到后端。
启动配置:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<AuthorizationHeaderMiddleware>();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(DEFAULT_AUTH_SCHEME)
.AddJwtBearer(DEFAULT_AUTH_SCHEME, cfg =>
{
cfg.Audience = Configuration["Authorization:JwtIssuer"];
cfg.RequireHttpsMetadata = false;
cfg.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = Configuration["Authorization:JwtIssuer"],
ValidAudience = Configuration["Authorization:JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authorization:JwtKey"])),
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
})
.AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["IdentityServer:Url"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration["IdentityServer:ApiName"];
options.SupportedTokens = SupportedTokens.Both;
options.SaveToken = false;
options.EnableCaching = false;
options.CacheDuration = TimeSpan.FromMinutes(10);
});
services.AddAuthorization(options =>
options.AddPolicy("protectedScope", policy =>
{
policy.AuthenticationSchemes = new List<string> { DEFAULT_AUTH_SCHEME };
policy.RequireAuthenticatedUser();
policy.RequireClaim("someclaim");
}));
}
AuthorizationHeaderMiddleware.cs:
public class AuthorizationHeaderMiddleware
{
private RequestDelegate _next;
private readonly IConfiguration _configuration;
public AuthorizationHeaderMiddleware(RequestDelegate next, IConfiguration configuration)
{
_configuration = configuration;
_next = next;
}
public async Task Invoke(HttpContext context)
{
// here I intend to get user from the (backend) DB based on "sub" claim from IdentityServer's token and set users claims from DB. Is this correct attitude?
var claims = new List<Claim> { new Claim("someclaim", "aaaa") };
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Authorization:JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_configuration["Authorization:JwtIssuer"],
_configuration["Authorization:JwtIssuer"],
claims,
signingCredentials: creds
);
var tokenGenerated = new JwtSecurityTokenHandler().WriteToken(token);
context.Request.Headers["Authorization"] = $"{DEFAULT_AUTH_SCHEME} {tokenGenerated}";
await _next.Invoke(context);
}
TestController.cs
[Authorize(Policy = "protectedScope", AuthenticationSchemes = DEFAULT_AUTH_SCHEME)]
public class TestController
{
[HttpGet]
public IActionResult TestAction()
{
return Ok();
}
}
如果我在 TestController 中请求测试操作,我会收到 401 Unauthorized。
我做错了什么?
这不是 使用多个 JWT 承载身份验证的副本
问题,因为我已经尝试过这个答案,但没有成功。此外,这是一种不同的情况,因为我想使用 IdentityServer 进行身份验证,后端 JWT 进行授权。
您只能使用 IdentityServerAuthentication
并在成功验证后将应用程序用户的声明添加到当前用户。您可以使用 OnTokenValidated
services.AddAuthentication()
.AddIdentityServerAuthentication(DEFAULT_AUTH_SCHEME, options =>
{
options.Authority = Configuration["IdentityServer:Url"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration["IdentityServer:ApiName"];
options.SupportedTokens = SupportedTokens.Both;
options.SaveToken = false;
options.EnableCaching = false;
options.CacheDuration = TimeSpan.FromMinutes(10);
options.JwtBearerEvents.OnTokenValidated = async context =>
{
// get subject from authenticated principal
var subject = context.Principal.FindFirst("sub");
// get claims from your database for the subject
var claims = new List<Claim> { new Claim("someclaim", "aaaa") };
// change the principal
context.Principal = new System.Security.Claims.ClaimsPrincipal(claims);
};
});
我有一个用于 SSO 的 .NET Core IdentityServer (IS),我想用它来验证我的 .NET Core(后端)-Angular(客户端)应用程序。我想在后端有一个 EF ApplicationUser,并通过自定义 backend-generated JWT 令牌在后端有一个基于声明的授权,这也适用于客户端授权。
在后端,我创建了一个中间件来检查所有请求的 "Authorization" header。如果 header 包含 IS 生成的令牌,我想将其交换为包含必要声明的自定义(后端)生成的令牌。客户端然后将此 header 用于后续请求到后端。
启动配置:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<AuthorizationHeaderMiddleware>();
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(DEFAULT_AUTH_SCHEME)
.AddJwtBearer(DEFAULT_AUTH_SCHEME, cfg =>
{
cfg.Audience = Configuration["Authorization:JwtIssuer"];
cfg.RequireHttpsMetadata = false;
cfg.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = Configuration["Authorization:JwtIssuer"],
ValidAudience = Configuration["Authorization:JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Authorization:JwtKey"])),
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
})
.AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = Configuration["IdentityServer:Url"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration["IdentityServer:ApiName"];
options.SupportedTokens = SupportedTokens.Both;
options.SaveToken = false;
options.EnableCaching = false;
options.CacheDuration = TimeSpan.FromMinutes(10);
});
services.AddAuthorization(options =>
options.AddPolicy("protectedScope", policy =>
{
policy.AuthenticationSchemes = new List<string> { DEFAULT_AUTH_SCHEME };
policy.RequireAuthenticatedUser();
policy.RequireClaim("someclaim");
}));
}
AuthorizationHeaderMiddleware.cs:
public class AuthorizationHeaderMiddleware
{
private RequestDelegate _next;
private readonly IConfiguration _configuration;
public AuthorizationHeaderMiddleware(RequestDelegate next, IConfiguration configuration)
{
_configuration = configuration;
_next = next;
}
public async Task Invoke(HttpContext context)
{
// here I intend to get user from the (backend) DB based on "sub" claim from IdentityServer's token and set users claims from DB. Is this correct attitude?
var claims = new List<Claim> { new Claim("someclaim", "aaaa") };
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Authorization:JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_configuration["Authorization:JwtIssuer"],
_configuration["Authorization:JwtIssuer"],
claims,
signingCredentials: creds
);
var tokenGenerated = new JwtSecurityTokenHandler().WriteToken(token);
context.Request.Headers["Authorization"] = $"{DEFAULT_AUTH_SCHEME} {tokenGenerated}";
await _next.Invoke(context);
}
TestController.cs
[Authorize(Policy = "protectedScope", AuthenticationSchemes = DEFAULT_AUTH_SCHEME)]
public class TestController
{
[HttpGet]
public IActionResult TestAction()
{
return Ok();
}
}
如果我在 TestController 中请求测试操作,我会收到 401 Unauthorized。
我做错了什么?
这不是 使用多个 JWT 承载身份验证的副本 问题,因为我已经尝试过这个答案,但没有成功。此外,这是一种不同的情况,因为我想使用 IdentityServer 进行身份验证,后端 JWT 进行授权。
您只能使用 IdentityServerAuthentication
并在成功验证后将应用程序用户的声明添加到当前用户。您可以使用 OnTokenValidated
services.AddAuthentication()
.AddIdentityServerAuthentication(DEFAULT_AUTH_SCHEME, options =>
{
options.Authority = Configuration["IdentityServer:Url"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration["IdentityServer:ApiName"];
options.SupportedTokens = SupportedTokens.Both;
options.SaveToken = false;
options.EnableCaching = false;
options.CacheDuration = TimeSpan.FromMinutes(10);
options.JwtBearerEvents.OnTokenValidated = async context =>
{
// get subject from authenticated principal
var subject = context.Principal.FindFirst("sub");
// get claims from your database for the subject
var claims = new List<Claim> { new Claim("someclaim", "aaaa") };
// change the principal
context.Principal = new System.Security.Claims.ClaimsPrincipal(claims);
};
});