分享

在.NET Core中使用Jwt对API进行认证

 头号码甲 2020-05-03

在.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 中。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多