在.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 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 Get() {await Task.CompletedTask;return "Welcome the Json Web Token Solucation!"; } [HttpGet("getToken")]public async Task 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(Configuration.GetSection("JwtSetting")); services.AddScoped(); services.AddScoped(); services.AddControllers(); } 在Client中,我一般会创建一个中间件用于接受认证结果,AspNetCore Jwt 源码中给我们提供了中间件,我们在进一步扩展,其源码定义如下: Task AuthenticateAsync( HttpContext context, scheme) =>().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();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 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 _httpContextCurrent = new AsyncLocal();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 中。 ![]() |
|