如何从 asp.net 核心中的 JWT 检索 ClaimsPrincipal
How to retrieve ClaimsPrincipal from JWT in asp.net core
在我的解决方案中,我有两个项目。 1) 网络 API 和 2) MVC。我正在使用 ASP.NET 核心。 API 发出 JWT 令牌,MVC 使用它来获取受保护的资源。我正在使用 openiddict 库来发布 JWT。在 MVC 项目中,在 AccountController Login 方法中,我想检索 ClaimsPrincipal(使用 JwtSecurityTokenHandler ValidateToken 方法)并分配给 HttpContext.User.Claims 和 HttpContext.User.Identity。我想将令牌存储在 session 中,对于成功登录后的每个请求,将其在 header 中传递给 Web API。我可以成功地发出 JWT 并在 MVC 项目中使用它,但是当我尝试检索 ClaimsPrincipal 时它会抛出一个错误。首先,我什至不确定从 JWT 中检索 ClaimsPrinciapal 是否是正确的方法。如果是,前进的方向是什么。
WebAPI.Startup.CS
public class Startup
{
public static string SecretKey => "MySecretKey";
public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IContainer ApplicationContainer { get; private set; }
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddCors();
services.AddMvc().AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); });
services.AddAutoMapper();
services.AddDbContext<MyDbContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("MyDbContext"));
options.UseOpenIddict();
});
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<TelescopeContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/Authorization/Token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.DisableHttpsRequirement();
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
});
var config = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); });
services.AddSingleton(sp => config.CreateMapper());
// Create the Autofac container builder.
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Populate the services.
builder.Populate(services);
// Build the container.
var container = builder.Build();
// Create and return the service provider.
return container.Resolve<IServiceProvider>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors(builder => builder.WithOrigins("http://localhost:9001/")
.AllowAnyOrigin());
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = "http://localhost:9001/",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "http://localhost:9000/",
RequireHttpsMetadata = false,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "http://localhost:9001/",
ValidateAudience = true,
ValidAudience = "http://localhost:9000",
ValidateLifetime = true,
IssuerSigningKey = SigningKey
}
});
app.UseOpenIddict();
app.UseMvcWithDefaultRoute();
applicationLifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}
}
WebAPI.AuthorizationController.cs 发布 JWT。
[Route("[controller]")]
public class AuthorizationController : Controller
{
private IUsersService UserService { get; set; }
public AuthorizationController(IUsersService userService)
{
UserService = userService;
}
[HttpPost("Token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
if (request.IsPasswordGrantType())
{
if (await UserService.AuthenticateUserAsync(new ViewModels.AuthenticateUserVm() { UserName = request.Username, Password = request.Password }) == false)
return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
var user = await UserService.FindByNameAsync(request.Username);
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, null);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, user.UserId.ToString(), OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Username, user.UserName, OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Email, user.EmailAddress, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.GivenName, user.FirstName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.MiddleName, user.MiddleName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.FamilyName, user.LastName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.EmailVerified, user.IsEmailConfirmed.ToString(), OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Audience, "http://localhost:9000", OpenIdConnectConstants.Destinations.AccessToken);
var principal = new ClaimsPrincipal(identity);
return SignIn(principal, OpenIdConnectServerDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
}
MVC.AccountController.cs 包含登录、GetTokenAsync 方法。
public class AccountController : Controller
{
public static string SecretKey => "MySecretKey";
public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVm vm, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var token = await GetTokenAsync(vm);
SecurityToken validatedToken = null;
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "http://localhost:9001/",
ValidateAudience = true,
ValidAudience = "http://localhost:9000",
ValidateLifetime = true,
IssuerSigningKey = SigningKey
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal principal = handler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
}
catch (Exception e)
{
throw;
}
}
return View(vm);
}
private async Task<TokenVm> GetTokenAsync(LoginVm vm)
{
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, $"http://localhost:9001/Authorization/Token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["username"] = vm.EmailAddress,
["password"] = vm.Password
});
var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadAsStringAsync();
//if (payload["error"] != null)
// throw new Exception("An error occurred while retriving an access tocken.");
return JsonConvert.DeserializeObject<TokenVm>(payload);
}
}
}
我得到错误:"IDX10501: Signature validation failed. Unable to match 'kid': '0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB', \ntoken: '{\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"0 -AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB\"}.{\"sub\":\"10\",\"username\":\"...
First of all, I am not even sure whether this retrieving of ClaimsPrinciapal from JWT is a right approach or not.
这可能不是我个人使用的方法。相反,我只是依靠 JWT 中间件为我从访问令牌中提取 ClaimsPrincipal
(不需要为此手动使用 JwtSecurityTokenHandler
)。
IdentityModel 抛出的异常实际上是由一个非常简单的根本原因引起的:您已将 OpenIddict 配置为使用临时 RSA 非对称签名密钥(通过 AddEphemeralSigningKey())
并注册了 对称签名JWT bearer options中的key,显然是行不通的场景
您有两种解决方法:
使用 AddSigningKey(SigningKey)
在 OpenIddict 选项中注册您的对称签名密钥,以便 OpenIddict 可以使用它。
使用非对称签名密钥(通过在生产中调用 AddEphemeralSigningKey()
或 AddSigningCertificate()
/AddSigningKey()
)并让 JWT 承载中间件使用它而不是您的对称签名密钥。为此,删除整个 TokenValidationParameters
配置以允许 IdentityModel 从 OpenIddict 的发现端点下载 public 签名密钥。
看到这个帖子是因为我一直在寻找同样的东西(虽然不是例外),并且接受的答案确实对 OP 有帮助,但是,它对我没有帮助:如何创建 ClaimsPrincipal
来自 JWT 令牌。
经过一些研究和挖掘,我找到了一种手动完成的方法(这是我的情况,我必须在特定情况下手动完成)。
为此,首先,使用 JwtSecurityTokenHandler
class 解析令牌:
var token = new JwtSecurityTokenHandler().ReadJwtToken(n.TokenEndpointResponse.AccessToken);
之后,您只需创建一个新的 ClaimsPrincipal 即可:
var identity = new ClaimsPrincipal(new ClaimsIdentity(token.Claims));
在我的具体情况下,我只需要更新我已经通过身份验证的用户的声明,所以我使用此代码:
var identity = (ClaimsIdentity)User.Identity;
identity.AddClaims(token.Claims);
希望有一天能帮到看标题答案的人。
在我的解决方案中,我有两个项目。 1) 网络 API 和 2) MVC。我正在使用 ASP.NET 核心。 API 发出 JWT 令牌,MVC 使用它来获取受保护的资源。我正在使用 openiddict 库来发布 JWT。在 MVC 项目中,在 AccountController Login 方法中,我想检索 ClaimsPrincipal(使用 JwtSecurityTokenHandler ValidateToken 方法)并分配给 HttpContext.User.Claims 和 HttpContext.User.Identity。我想将令牌存储在 session 中,对于成功登录后的每个请求,将其在 header 中传递给 Web API。我可以成功地发出 JWT 并在 MVC 项目中使用它,但是当我尝试检索 ClaimsPrincipal 时它会抛出一个错误。首先,我什至不确定从 JWT 中检索 ClaimsPrinciapal 是否是正确的方法。如果是,前进的方向是什么。
WebAPI.Startup.CS
public class Startup
{
public static string SecretKey => "MySecretKey";
public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IContainer ApplicationContainer { get; private set; }
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddCors();
services.AddMvc().AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); });
services.AddAutoMapper();
services.AddDbContext<MyDbContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("MyDbContext"));
options.UseOpenIddict();
});
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<TelescopeContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/Authorization/Token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.DisableHttpsRequirement();
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
});
var config = new MapperConfiguration(cfg => { cfg.AddProfile(new MappingProfile()); });
services.AddSingleton(sp => config.CreateMapper());
// Create the Autofac container builder.
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Populate the services.
builder.Populate(services);
// Build the container.
var container = builder.Build();
// Create and return the service provider.
return container.Resolve<IServiceProvider>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors(builder => builder.WithOrigins("http://localhost:9001/")
.AllowAnyOrigin());
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = "http://localhost:9001/",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "http://localhost:9000/",
RequireHttpsMetadata = false,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "http://localhost:9001/",
ValidateAudience = true,
ValidAudience = "http://localhost:9000",
ValidateLifetime = true,
IssuerSigningKey = SigningKey
}
});
app.UseOpenIddict();
app.UseMvcWithDefaultRoute();
applicationLifetime.ApplicationStopped.Register(() => this.ApplicationContainer.Dispose());
}
}
WebAPI.AuthorizationController.cs 发布 JWT。
[Route("[controller]")]
public class AuthorizationController : Controller
{
private IUsersService UserService { get; set; }
public AuthorizationController(IUsersService userService)
{
UserService = userService;
}
[HttpPost("Token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
if (request.IsPasswordGrantType())
{
if (await UserService.AuthenticateUserAsync(new ViewModels.AuthenticateUserVm() { UserName = request.Username, Password = request.Password }) == false)
return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
var user = await UserService.FindByNameAsync(request.Username);
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme, OpenIdConnectConstants.Claims.Name, null);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, user.UserId.ToString(), OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Username, user.UserName, OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Email, user.EmailAddress, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.GivenName, user.FirstName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.MiddleName, user.MiddleName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.FamilyName, user.LastName, OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.EmailVerified, user.IsEmailConfirmed.ToString(), OpenIdConnectConstants.Destinations.IdentityToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Audience, "http://localhost:9000", OpenIdConnectConstants.Destinations.AccessToken);
var principal = new ClaimsPrincipal(identity);
return SignIn(principal, OpenIdConnectServerDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
}
MVC.AccountController.cs 包含登录、GetTokenAsync 方法。
public class AccountController : Controller
{
public static string SecretKey => "MySecretKey";
public static SymmetricSecurityKey SigningKey => new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVm vm, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var token = await GetTokenAsync(vm);
SecurityToken validatedToken = null;
TokenValidationParameters validationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "http://localhost:9001/",
ValidateAudience = true,
ValidAudience = "http://localhost:9000",
ValidateLifetime = true,
IssuerSigningKey = SigningKey
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
try
{
ClaimsPrincipal principal = handler.ValidateToken(token.AccessToken, validationParameters, out validatedToken);
}
catch (Exception e)
{
throw;
}
}
return View(vm);
}
private async Task<TokenVm> GetTokenAsync(LoginVm vm)
{
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, $"http://localhost:9001/Authorization/Token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["username"] = vm.EmailAddress,
["password"] = vm.Password
});
var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
var payload = await response.Content.ReadAsStringAsync();
//if (payload["error"] != null)
// throw new Exception("An error occurred while retriving an access tocken.");
return JsonConvert.DeserializeObject<TokenVm>(payload);
}
}
}
我得到错误:"IDX10501: Signature validation failed. Unable to match 'kid': '0-AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB', \ntoken: '{\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"0 -AY7TPAUE2-ZVLUVQMMUJFJ54IMIB70E-XUSYIB\"}.{\"sub\":\"10\",\"username\":\"...
First of all, I am not even sure whether this retrieving of ClaimsPrinciapal from JWT is a right approach or not.
这可能不是我个人使用的方法。相反,我只是依靠 JWT 中间件为我从访问令牌中提取 ClaimsPrincipal
(不需要为此手动使用 JwtSecurityTokenHandler
)。
IdentityModel 抛出的异常实际上是由一个非常简单的根本原因引起的:您已将 OpenIddict 配置为使用临时 RSA 非对称签名密钥(通过 AddEphemeralSigningKey())
并注册了 对称签名JWT bearer options中的key,显然是行不通的场景
您有两种解决方法:
使用
AddSigningKey(SigningKey)
在 OpenIddict 选项中注册您的对称签名密钥,以便 OpenIddict 可以使用它。使用非对称签名密钥(通过在生产中调用
AddEphemeralSigningKey()
或AddSigningCertificate()
/AddSigningKey()
)并让 JWT 承载中间件使用它而不是您的对称签名密钥。为此,删除整个TokenValidationParameters
配置以允许 IdentityModel 从 OpenIddict 的发现端点下载 public 签名密钥。
看到这个帖子是因为我一直在寻找同样的东西(虽然不是例外),并且接受的答案确实对 OP 有帮助,但是,它对我没有帮助:如何创建 ClaimsPrincipal
来自 JWT 令牌。
经过一些研究和挖掘,我找到了一种手动完成的方法(这是我的情况,我必须在特定情况下手动完成)。
为此,首先,使用 JwtSecurityTokenHandler
class 解析令牌:
var token = new JwtSecurityTokenHandler().ReadJwtToken(n.TokenEndpointResponse.AccessToken);
之后,您只需创建一个新的 ClaimsPrincipal 即可:
var identity = new ClaimsPrincipal(new ClaimsIdentity(token.Claims));
在我的具体情况下,我只需要更新我已经通过身份验证的用户的声明,所以我使用此代码:
var identity = (ClaimsIdentity)User.Identity;
identity.AddClaims(token.Claims);
希望有一天能帮到看标题答案的人。