在. 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 中.
来源: https://www.cnblogs.com/ZaraNet/p/11976611.html