ASP.NET Core JWT 和声明
Posted
技术标签:
【中文标题】ASP.NET Core JWT 和声明【英文标题】:ASP.NET Core JWT and Claims 【发布时间】:2018-05-08 11:29:02 【问题描述】:我有一个关于 ASP.NET Core 和 Claims 中的 JWT 身份验证的问题,因为我不知道我是否正确获取了所有内容。
当我在 ASP.NET 中创建 JWT 令牌时,我添加了一些声明,其中一些可以是自定义的。当带有 JWT 令牌的请求从客户端发送到 API 时会发生什么。 User.Claims 如何填写?它是否使用从 JWT 读取的声明?
我想创建一个自定义身份提供程序(不想使用由 ASP.NET 提供的),并使用我自己的用户数据、角色等表。我不想存储完成所需的所有重要数据JWT 令牌中的策略(令牌中存储的信息量很重要,也很安全)。是否可以在 JWT 令牌中仅存储基本声明(如用户 ID、名称等),然后重新获取其他所需的数据 DB/缓存?除此之外,我想使用 [Authorize] 的标准机制和 Policy 机制。
如何让这一切发挥作用:自定义用户身份 + JWT + 标准 ASP.NET 基于策略的授权 + 在每次请求时从数据库/缓存中获取声明?如何做到这一点?
【问题讨论】:
【参考方案1】:Asp 网络核心
第一步是编写配置Jwt认证的方法:
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime. As we're creating the tokens locally and validating
// them on the same machines which should have synchronised time,
// this can be set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
;
);
现在在Startup.cs的ConfigureServices()方法中,我们可以调用ConfigureJwtAuthService()方法来配置Jwt认证。
这是完整的Startup.cs:
using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace ExpertCodeBlogWebApp
public class Startup
public Startup(IConfiguration configuration)
Configuration = configuration;
public IConfiguration Configuration get;
// This method gets called by the runtime. Use this method to add
// services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
services.AddMvc();
// Configure jwt autenticazione
ConfigureJwtAuthService(services);
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
// Create the Autofac container builder for dependency injection
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Return ServiceProvider
var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime.
// As we're creating the tokens locally and validating them on the
// same machines which should have synchronised time, this can be
// set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
;
);
// This method gets called by the runtime. Use this method to configure
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
HotModuleReplacement = true
);
else
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseMvc(routes =>
routes.MapRoute(
name: "default",
template: "controller=Home/action=Index/id?");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new controller = "Home", action = "Index" );
);
// For dependency injection.
public class AutofacModule : Module
// Dependency Injection with Autofact
protected override void Load(ContainerBuilder builder)
builder.RegisterType<UserRepository>().As<IUserRepository>()
.SingleInstance();
JwtController.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace ExpertCodeBlogWebApp.Controllers
[Route("api/[controller]")]
public class JwtController : Controller
#region Private Members
// JWT-related members
private TimeSpan TokenExpiration;
private SigningCredentials SigningCredentials;
// EF and Identity members, available through DI
private MyDbContext DbContext;
private IUserRepository _userRepository;
private readonly ILogger _logger;
#endregion Private Members
#region Static Members
private static readonly string PrivateKey = "my_PrivateKey";
public static readonly SymmetricSecurityKey SecurityKey =
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
public static readonly string Issuer = "my_Issuer";
public static readonly string Audience = "my_Audience";
#endregion Static Members
#region Constructor
// I have used Autofac in the Startup.cs for dependency injection)
public JwtController(
MyDbContext dbContext,
IUserRepository userRepository,
ILogger<JwtController> logger)
_logger = logger;
_userRepository = userRepository;
// Instantiate JWT-related members
TokenExpiration = TimeSpan.FromMinutes(10);
SigningCredentials = new SigningCredentials(SecurityKey,
SecurityAlgorithms.HmacSha256);
// Instantiate through Dependency Injection with Autofact
DbContext = dbContext;
#endregion Constructor
#region Public Methods
// Manages the request for a new authentication or the refresh of an
// already established one
[HttpPost("token")]
public async Task<IActionResult>
Authentication([FromBody]JwtRequestViewModel jwt)
if (ModelState.IsValid)
string grantType = jwt.GrantType;
if (grantType == "password")
string userName = jwt.UserName;
string password = jwt.Password;
// Password check required
var user = await
_userRepository.GetUserInfoWithCheckPwd(userName, password);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of user.Name expired!");
if (UserEnabled(user))
return await GenerateToken(user);
else
return BadRequest("User name or password invalid.");
else if (grantType == "refresh_token")
string userName = jwt.UserName;
// Refresh token (no password check required)
var user = await _userRepository.GetUserInfoByName(userName);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of user.Name expired!");
string token = jwt.Token;
if (token == user.Token)
// Generate token and send it via a json-formatted string
return await GenerateToken(user);
else
return BadRequest("User token invalid.");
else
return BadRequest("Authentication type invalid.");
else
return BadRequest("Request invalid.");
#endregion Public Methods
#region Private Methods
private bool UserExpired(Users utente)
if (utente != null)
return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
return true;
private bool UserEnabled(Users utente)
if (utente != null)
return utente.Enabled == true;
return false;
private JsonSerializerSettings DefaultJsonSettings
get
return new JsonSerializerSettings()
Formatting = Formatting.Indented
;
private async Task<IActionResult> GenerateToken(Users user)
try
if (user != null)
var handler = new JwtSecurityTokenHandler();
DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Name, "TokenAuth"),
new[] new Claim("ID", user.Id.ToString())
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
Issuer = JwtController.Issuer,
Audience = JwtController.Audience,
SigningCredentials = SigningCredentials,
Subject = identity,
Expires = newTokenExpiration
);
string encodedToken = handler.WriteToken(securityToken);
// Update token data on database
await _userRepository.UpdateTokenData(user.Name, encodedToken,
newTokenExpiration);
// Build the json response
// (I use Automapper to maps an object into another object)
var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
jwtResponse.AccessToken = encodedToken;
jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
return Ok(jwtResponse);
return NotFound();
catch(Exception e)
return BadRequest(e.Message);
#endregion
在我的项目中,我使用 Angular。 Angular 调用 JwtController 方法:
login(userName: string, password: string)
return this.getLoginEndpoint(userName, password)
.map((response: Response) => this.processLoginResponse(response));
getLoginEndpoint(userName: string, password: string): Observable<Response>
// Body
// JwtRequest is a model class that I use to send info to the controller
let jwt = new JwtRequest();
jwt.GrantType = "password";
jwt.UserName = userName;
jwt.Password = password;
jwt.ClientId = "my_Issuer";
// Post requiest (I use getAuthHeader that attach to the header the
// authentication token, but it can also be omitted because it is ignored
// by the JwtController
return this.http.post(this.loginUrl, JSON.stringify(jwt),
this.getAuthHeader(true))
protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
// Hera I use this.authService.accessToken that is a my service where
// I have store the token received from the server
let headers = new Headers(
'Authorization': 'Bearer ' + this.authService.accessToken );
if (includeJsonContentType)
headers.append("Content-Type", "application/json");
headers.append("Accept", `application/vnd.iman.v01+json,
application/json, text/plain, */*`);
headers.append("App-Version", "01");
return new RequestOptions( headers: headers );
private processLoginResponse(response: Response)
// process the response..
在您希望只有经过身份验证的用户才能访问的控制器类(或方法)上(而不是在您的 JwtController 上,因为它的方法必须可供所有用户访问)您可以设置:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
要从 Angular 调用需要身份验证的控制器方法,您需要使用 getAuthHeader() 方法将令牌附加到标头中。
希望这篇文章对你有帮助。
【讨论】:
【参考方案2】:是的,它使用存储在 jwt 令牌中的声明 在创建令牌时查看存储在令牌中的声明的 httpcontext 对象
这个链接也有帮助https://joonasw.net/view/adding-custom-claims-aspnet-core-2
【讨论】:
请在此处写下解决方案,而不是包含将来可能会损坏的链接。谢谢!以上是关于ASP.NET Core JWT 和声明的主要内容,如果未能解决你的问题,请参考以下文章
Jwt 和 ASP.NET CORE 授权 AspNetRoleClaims
如何使用 ASP.NET Core 中的声明检查 JWT 中的权限?
ASP.NET Core Jwt 实现 signinmanager 声明