• 在.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的服务代码:

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

  • 相关阅读:
    过去式和过去进行式
    现在式和现在进行式
    英文文法的最基本规则
    Vue 标签中的ref属性和refs
    APICloud
    小程序
    React 传值 组件传值 之间的关系
    css clip样式 属性功能及作用
    小程序点击预览 为什么显示空白
    小程序
  • 原文地址:https://www.cnblogs.com/ZaraNet/p/11976611.html
Copyright © 2020-2023  润新知