在.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 类型的结果,其定义部分是这样的,我们就可以将计就计,给他来个连环套。

连环套直接接受 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>