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。
有二個常用的。