JWT
JSON Web Token 经过数字签名后,无法伪造,一个能够在各方之间安全的传输JSON对象的开放标准(RFC 7519 )
创建项目和解决方案
dotnet new webapi -n SampleApi
cd SampleApi
dotnet new sln -n SampleApp
dotnet sln add .\SampleApi.csproj
引用包
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
该包已经依赖Microsoft.IdentityModel.Tokens、System.IdentityModel.Tokens.Jwt,该包由Azure AD 团队提供,所以不在aspnetcore6 运行时中。
或直接修改jwtaspnetcore.csproj,引用包
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
"Authentication" : {
"JwtBearer" : {
"Issuer" : "http://api.sampleapi.com" ,
"Audience" : "SampleApi" ,
"SecurityKey" : "SecurityKey23456"
}
}
Issuer:令牌的颁发者。一般就写成域名,实际可任意
Audience 颁发给谁。一般写成项目名,实际可任意
SecurityKey:签名验证的KEY;至少 128bit ,即16个英文字符以上,实际可任意英文字符
定义一个JwtSettings
public class JwtSettings
{
public JwtSettings (byte[] key, string issuer, string audience)
{
Key = key;
Issuer = issuer;
Audience = audience;
}
public string Issuer { get; }
public string Audience { get; }
public byte[] Key { get; }
public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
{
ValidateIssuer = true ,
ValidateAudience = true ,
ValidateIssuerSigningKey = true ,
ValidateLifetime = true ,
ValidIssuer = Issuer,
ValidAudience = Audience,
IssuerSigningKey = new SymmetricSecurityKey(Key)
};
public static JwtSettings FromConfiguration (IConfiguration configuration)
{
var issuser = configuration["Authentication:JwtBearer:Issuer" ] ?? "default_issuer" ;
var auidence = configuration["Authentication:JwtBearer:Audience" ] ?? "default_auidence" ;
var securityKey = configuration["Authentication:JwtBearer:SecurityKey" ] ?? "default_securitykey" ;
byte[] key = Encoding.ASCII.GetBytes(securityKey);
return new JwtSettings(key, issuser, auidence);
}
}
中间件Middleware引用
app.UseAuthentication();
app.UseAuthorization();
定义JWT扩展方法服务注入
public static IServiceCollection AddJwt (this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IStorageUserService, StorageUserService>();
var jwtSettings = JwtSettings.FromConfiguration(configuration);
services.AddSingleton(jwtSettings);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => options.TokenValidationParameters = jwtSettings.TokenValidationParameters);
return services;
}
引用服务
services.AddJwt(Configuration);
定义一个数据库的实体类,数据库访问 为模拟数据
public class SysUser
{
public int Id { get; set ; }
public string UserName { get; set ; }
}
public interface IStorageUserService
{
Task <SysUser > CheckPasswordAsync (LoginInfo loginInfo);
}
public class StorageUserService : IStorageUserService
{
public async Task <SysUser > CheckPasswordAsync (LoginInfo loginInfo)
{
return await Task .FromResult (
new SysUser
{
Id = new Random ().Next (10000 ),
UserName = loginInfo.UserName
}
);
}
}
AuthController登录GenerateToken
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SampleApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace SampleApi.Auth;
[ApiController]
[Route("/api/[controller]/[action]" )]
[AllowAnonymous]
public class AuthController : ControllerBase
{
private readonly IStorageUserService _userService;
private readonly JwtSettings _jwtSettings;
public AuthController (JwtSettings jwtSettings, IStorageUserService userService)
{
_jwtSettings = jwtSettings;
_userService = userService;
}
[HttpPost]
public async Task<IActionResult> GenerateToken (LoginInfo loginInfo)
{
SysUser user = await _userService.CheckPasswordAsync(loginInfo);
if (user == null)
{
return Ok(new
{
Status = false ,
Message = "账号或密码错误"
});
}
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
var key = new SymmetricSecurityKey(_jwtSettings.Key);
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(120 ),
signingCredentials: creds
);
return Ok(new
{
Status = true ,
Token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}
aspnetcore6默认集成了swagger,直接运行项目,实际上为模拟数据库请求,所以点击登录接口即可。
{
"status" : true ,
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijc4NjciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNjQzMDMyNzA1LCJpc3MiOiJodHRwOi8vYXBpLnNhbXBsZWFwaS5jb20iLCJhdWQiOiJTYW1wbGVBcGkifQ.Rl8XAt2u0aZRxEJw2mVUnV6S9WzQ65qUYjqXDTneCxE"
}
当使用Swagger测试时,增加,可配置全局请求头。增加一个扩展方法。
services.AddSwagger(Configuration);
public static IServiceCollection AddSwagger (this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
try
{
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml" ), true );
}
catch (Exception ex)
{
Log.Warning(ex.Message);
}
options.SwaggerDoc("v1" , new OpenApiInfo
{
Title = "SampleApp - HTTP API" ,
Version = "v1" ,
Description = "The SampleApp Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "Bearer" ,
Type = ReferenceType.SecurityScheme
}
},
Array.Empty<string >()
}
});
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"" ,
Name = "Authorization" ,
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});
services.AddEndpointsApiExplorer();
return services;
}
获取当前用户信息
[HttpGet]
[AllowAnonymous]
public CurrentUser DecodeToken (string token)
{
var jwtTokenHandler = new JwtSecurityTokenHandler();
if (jwtTokenHandler.CanReadToken(token))
{
JwtPayload jwtPayload = new JwtSecurityTokenHandler().ReadJwtToken(token).Payload;
string ? userIdOrNull = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.NameIdentifier)?.Value;
string ? UserName = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value;
CurrentUser currentUser = new CurrentUser
{
UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
UserName = UserName
};
return currentUser;
}
return null;
}
根据请求头获取用户信息
IStorageUserService增加接口,StorageUserService的实现,创建一个CurrentUser类
public class StorageUserService : IStorageUserService
{
private readonly IHttpContextAccessor _contextAccessor;
public StorageUserService (IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public async Task<CurrentUser> GetUserByRequestContext ()
{
var user = _contextAccessor.HttpContext.User;
string ? userIdOrNull = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
string ? UserName = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
CurrentUser currentUser = new CurrentUser
{
IsAuthenticated = user.Identity.IsAuthenticated,
UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
UserName = UserName
};
return await Task.FromResult(currentUser);
}
}
public class CurrentUser
{
public bool IsAuthenticated { get; set ; }
public int ? UserId { get; set ; }
public string ? UserName { get; set ; }
}
public interface IStorageUserService
{
Task<CurrentUser> GetUserByRequestContext () ;
}
AuthController调用服务
[HttpGet]
[Authorize]
public async Task<CurrentUser> GetUserByRequestContext ()
{
return await _userService.GetUserByRequestContext();
}
在swagger右上角,点击Authorize,header的参数结构: “Authorization: Bearer+空格+ {token}”
开源地址
SampleApp/SampleApi at master · luoyunchong/SampleApp (github.com)
.NET +JWT
JSON Web Token Libraries – jwt.io 可以看到,.NET有6个类库实现了JWT。
有二个常用的。
微软 Azure团队的实现:AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet: IdentityModel extensions for .Net (github.com)
jwt-dotnet/jwt: Jwt.Net, a JWT (JSON Web Token) implementation for .NET (github.com)