在.NET Core中想給API進行安全認證,最簡單的無非就是Jwt,悠然記得一年前寫的Jwt Demo,現在拿回來改成.NET Core的,但是在編碼上的改變並不大,因為Jwt已經足夠強大了。在項目中分為 DotNetCore_Jwt_Server 以及 DotNetCore_Jwt_Client ...
在.NET Core中想給API進行安全認證,最簡單的無非就是Jwt,悠然記得一年前寫的Jwt Demo,現在拿回來改成.NET Core的,但是在編碼上的改變並不大,因為Jwt已經足夠強大了。在項目中分為 DotNetCore_Jwt_Server 以及 DotNetCore_Jwt_Client ,從名字就可以看出來是啥意思,博客園高手雲集,我就不多訴說,這篇博客就當是一篇記錄。
當然本案例是Server&Client雙項目,如果你要合成自己發證的形式,那你就自己改下代碼玩。
在Server層都會有分發Token的服務,在其中做了用戶密碼判斷,隨後根據 Claim 生成 jwtToken 的操作。
其生成Token的服務代碼:
namespace DotNetCore_Jwt_Server.Services { public interface ITokenService { string GetToken(User user); } public class TokenService : ITokenService { private readonly JwtSetting _jwtSetting; public TokenService(IOptions<JwtSetting> option) { _jwtSetting = option.Value; } public string GetToken(User user) { //創建用戶身份標識,可按需要添加更多信息 var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim("id", user.Id.ToString(), ClaimValueTypes.Integer32), new Claim("name", user.Name), new Claim("admin", user.IsAdmin.ToString(),ClaimValueTypes.Boolean) }; //創建令牌 var token = new JwtSecurityToken( issuer: _jwtSetting.Issuer, audience: _jwtSetting.Audience, signingCredentials: _jwtSetting.Credentials, claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; } } }
在獲取Token中我們依賴註入服務到控制器中,隨後依賴它進行認證並且分發Token,
public class ValuesController : ControllerBase { private readonly IUserService _userService; private readonly ITokenService _tokenService; public ValuesController(IUserService userService, ITokenService tokenService) { _userService = userService; _tokenService = tokenService; } [HttpGet] public async Task<string> Get() { await Task.CompletedTask; return "Welcome the Json Web Token Solucation!"; } [HttpGet("getToken")] public async Task<string> GetTokenAsync(string name, string password) { var user = await _userService.LoginAsync(name, password); if (user == null) return "Login Failed"; var token = _tokenService.GetToken(user); var response = new { Status = true, Token = token, Type = "Bearer" }; return JsonConvert.SerializeObject(response); } }
隨後,我們又在項目配置文件中填寫了幾個欄位,相關備註已註釋,但值得說明的是有位朋友問我,伺服器端生成的Token不需要保存嗎,比如Redis或者是Session,其實Jwt Token是無狀態的,他們之間的對比第一個是你的token解密出來的信息正確與否,第二部則是看看你 SecurityKey 是否正確,就這樣他們的認證才會得出結果。
"JwtSetting": { "SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰 "Issuer": "jwtIssuertest", // 頒發者 "Audience": "jwtAudiencetest", // 接收者 "ExpireSeconds": 20000 // 過期時間 }
隨後我們需要DI兩個介面以及初始化設置相關欄位。
public void ConfigureServices(IServiceCollection services) { services.Configure<JwtSetting>(Configuration.GetSection("JwtSetting")); services.AddScoped<IUserService, UserService>(); services.AddScoped<ITokenService, TokenService>(); services.AddControllers(); }
在Client中,我一般會創建一個中間件用於接受認證結果,AspNetCore Jwt 源碼中給我們提供了中間件,我們在進一步擴展,其源碼定義如下:
/// <summary> /// Extension methods to expose Authentication on HttpContext. /// </summary> public static class AuthenticationHttpContextExtensions {/// <summary> /// Extension method for authenticate. /// </summary> /// <param name="context">The <see cref="HttpContext"/> context.</param> /// <param name="scheme">The name of the authentication scheme.</param> /// <returns>The <see cref="AuthenticateResult"/>.</returns> public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) => context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
}
其該擴展會返回一個 AuthenticateResult 類型的結果,其定義部分是這樣的,我們就可以將計就計,給他來個連環套。
連環套直接接受 httpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme) 返回回來的值,隨後進行判斷返回相應的Http響應碼。
public class AuthMiddleware { private readonly RequestDelegate _next; public AuthMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { var result = await httpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); if (!result.Succeeded) { httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; await httpContext.Response.WriteAsync("Authorize error"); } else { httpContext.User = result.Principal; await _next.Invoke(httpContext); } } }
當然你也得在Client中添加認證的一些設置,它和Server端的 IssuerSigningKey 一定要對應,否則認證失敗。
public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); services.AddScoped<IIdentityService, IdentityService>(); var jwtSetting = new JwtSetting(); Configuration.Bind("JwtSetting", jwtSetting); services.AddCors(options => { options.AddPolicy("any", builder => { builder.AllowAnyOrigin() //允許任何來源的主機訪問 .AllowAnyMethod() .AllowAnyHeader(); }); }); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = jwtSetting.Issuer, ValidAudience = jwtSetting.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)), 預設 300s ClockSkew = TimeSpan.Zero }; }); services.AddControllers(); }
隨後,你就可以編寫帶需認證才可以訪問的API了,如果認證失敗則會返回401的錯誤響應。
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly IIdentityService _identityService; public ValuesController(IIdentityService identityService) { _identityService = identityService; } [HttpGet] [Authorize] public async Task<string> Get() { await Task.CompletedTask; return $"{_identityService.GetUserId()}:{_identityService.GetUserName()}"; }
值得一提的是,我們可以根據 IHttpContextAccessor 以來註入到我們的Service或者Api中,它是一個當前請求的認證信息上下文,這將有利於你獲取用戶信息去做該做的事情。
public class IdentityService : IIdentityService { private readonly IHttpContextAccessor _context; public IdentityService(IHttpContextAccessor context) { _context = context; } public int GetUserId() { var nameId = _context.HttpContext.User.FindFirst("id"); return nameId != null ? Convert.ToInt32(nameId.Value) : 0; } public string GetUserName() { return _context.HttpContext.User.FindFirst("name")?.Value; } }
在源碼中該類的定義如下,實際上我們可以看到只不過是判斷了當前的http上下文吧,所以我們得出,如果認證失敗,上下本信息也是空的。
public class HttpContextAccessor : IHttpContextAccessor { private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>(); public HttpContext HttpContext { get { return _httpContextCurrent.Value?.Context; } set { var holder = _httpContextCurrent.Value; if (holder != null) { // Clear current HttpContext trapped in the AsyncLocals, as its done. holder.Context = null; } if (value != null) { // Use an object indirection to hold the HttpContext in the AsyncLocal, // so it can be cleared in all ExecutionContexts when its cleared. _httpContextCurrent.Value = new HttpContextHolder { Context = value }; } } } private class HttpContextHolder { public HttpContext Context; } }
如果要通過js來測試代碼,您可以添加請求頭來進行認證,beforeSend是在請求之前的事件。
beforeSend : function(request) { request.setRequestHeader("Authorization", sessionStorage.getItem("Authorization")); }
好了,今天就說到這,代碼地址在https://github.com/zaranetCore/DotNetCore_Jwt 中。