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" />
- appsettings.json
"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;
}
/// <summary>
///令牌的颁发者
/// </summary>
public string Issuer { get; }
/// <summary>
/// 颁发给谁
/// </summary>
public string Audience { get; }
public byte[] Key { get; }
public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
{
//验证Issuer和Audience
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
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
{
/// <summary>
/// 根据登录验证用户
/// </summary>
/// <param name="loginInfo"></param>
/// <returns></returns>
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;
/// <summary>
/// 登录认证个人信息
/// </summary>
[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;
}
/// <summary>
/// 登录,生成访问Toekn
/// </summary>
/// <param name="loginInfo"></param>
/// <returns></returns>
[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", //jwt默认的参数名称
In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey
});
});
services.AddEndpointsApiExplorer();
return services;
}
获取当前用户信息
/// <summary>
/// 编码Token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[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
{
/// <summary>
/// 是否登录
/// </summary>
public bool IsAuthenticated { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public int? UserId { get; set; }
/// <summary>
/// 用户名
/// </summary>
public string? UserName { get; set; }
}
public interface IStorageUserService
{
/// <summary>
/// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
/// </summary>
/// <returns></returns>
Task<CurrentUser> GetUserByRequestContext();
}
AuthController调用服务
/// <summary>
/// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
/// </summary>
/// <returns></returns>
[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。
有二个常用的。