在.NET Core中使用Jwt對API進行認證

在.NET Core中想給API進行安全認證,最簡單的無非就是Jwt,悠然記得一年前寫的Jwt Demo,現在拿回來改成.NET Core的,但是在編碼上的改變並不大,因為Jwt已經足夠強大了。在項目中分為 DotNetCore_Jwt_Server 以及 DotNetCore_Jwt_Client ,從名字就可以看出來是啥意思,博客園高手雲集,我就不多訴說,這篇博客就當是一篇記錄。

  當然本案例是Server&Client雙項目,如果你要合成自己發證的形式,那你就自己改下代碼玩。

  在Server層都會有分發Token的服務,在其中做了用戶密碼判斷,隨後根據 Claim 生成 jwtToken 的操作。

  其生成Token的服務代碼:

<code>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;
}
}
}/<jwtsetting>/<code>

在獲取Token中我們依賴注入服務到控制器中,隨後依賴它進行認證並且分發Token,

<code>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);
}
}/<string>/<string>/<code>

  隨後,我們又在項目配置文件中填寫了幾個字段,相關備註已註釋,但值得說明的是有位朋友問我,服務器端生成的Token不需要保存嗎,比如Redis或者是Session,其實Jwt Token是無狀態的,他們之間的對比第一個是你的token解密出來的信息正確與否,第二部則是看看你 SecurityKey 是否正確,就這樣他們的認證才會得出結果。

<code>"JwtSetting": {
"SecurityKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰
"Issuer": "jwtIssuertest", // 頒發者
"Audience": "jwtAudiencetest", // 接收者
"ExpireSeconds": 20000 // 過期時間
}/<code>

  隨後我們需要DI兩個接口以及初始化設置相關字段。

<code>public void ConfigureServices(IServiceCollection services)
{
services.Configure<jwtsetting>(Configuration.GetSection("JwtSetting"));
services.AddScoped<iuserservice>();
services.AddScoped<itokenservice>();
services.AddControllers();
}/<itokenservice>/<iuserservice>/<jwtsetting>/<code>

  在Client中,我一般會創建一箇中間件用於接受認證結果,AspNetCore Jwt 源碼中給我們提供了中間件,我們在進一步擴展,其源碼定義如下:

<code>/// <summary>
/// Extension methods to expose Authentication on HttpContext.
/// /<summary>
public static class AuthenticationHttpContextExtensions
{/// <summary>
/// Extension method for authenticate.
/// /<summary>
/// <param>The context.
/// <param>The name of the authentication scheme.
/// <returns>The ./<returns>
public static Task<authenticateresult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<iauthenticationservice>().AuthenticateAsync(context, scheme);
  }/<iauthenticationservice>/<authenticateresult>/<code>

  其該擴展會返回一個 AuthenticateResult 類型的結果,其定義部分是這樣的,我們就可以將計就計,給他來個連環套。

在.NET Core中使用Jwt對API進行認證

連環套直接接受 httpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme) 返回回來的值,隨後進行判斷返回相應的Http響應碼。

<code>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);
}
}
}/<code>

  當然你也得在Client中添加認證的一些設置,它和Server端的 IssuerSigningKey 一定要對應,否則認證失敗。

<code>        public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<iidentityservice>();
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();
}/<iidentityservice>/<code>

  隨後,你就可以編寫帶需認證才可以訪問的API了,如果認證失敗則會返回401的錯誤響應。

<code>  [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()}";
}/<string>/<code>

  值得一提的是,我們可以根據 IHttpContextAccessor 以來注入到我們的Service或者Api中,它是一個當前請求的認證信息上下文,這將有利於你獲取用戶信息去做該做的事情。

<code>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;
}
}/<code>

  在源碼中該類的定義如下,實際上我們可以看到只不過是判斷了當前的http上下文吧,所以我們得出,如果認證失敗,上下本信息也是空的。

<code>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;
}
}/<httpcontextholder>/<httpcontextholder>/<code>

  如果要通過js來測試代碼,您可以添加請求頭來進行認證,beforeSend是在請求之前的事件。

<code>beforeSend : function(request) {
  request.setRequestHeader("Authorization", sessionStorage.getItem("Authorization"));
}/<code>
在.NET Core中使用Jwt對API進行認證


分享到:


相關文章: