13°

在.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)
            };
        </span><span style="color: #008000;">//</span><span style="color: #008000;">创建令牌</span>
        <span style="color: #0000ff;">var</span> token = <span style="color: #0000ff;">new</span><span style="color: #000000;"> JwtSecurityToken(
                issuer: _jwtSetting.Issuer,
                audience: _jwtSetting.Audience,
                signingCredentials: _jwtSetting.Credentials,
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds)
            );
        </span><span style="color: #0000ff;">string</span> jwtToken = <span style="color: #0000ff;">new</span><span style="color: #000000;"> JwtSecurityTokenHandler().WriteToken(token);
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> jwtToken;
    }
}

}

在获取Token中我们依赖注入服务到控制器中,随后依赖它进行认证并且分发Token,

public class ValuesController : ControllerBase
    {
        private readonly IUserService _userService;
        private readonly ITokenService _tokenService;
    </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> ValuesController(IUserService userService,
        ITokenService tokenService)
    {
        _userService </span>=<span style="color: #000000;"> userService;
        _tokenService </span>=<span style="color: #000000;"> tokenService;
    }
    [HttpGet]
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task&lt;<span style="color: #0000ff;">string</span>&gt;<span style="color: #000000;"> Get()
    {
        </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> Task.CompletedTask;
        </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">Welcome the Json Web Token Solucation!</span><span style="color: #800000;">"</span><span style="color: #000000;">;
    }
    [HttpGet(</span><span style="color: #800000;">"</span><span style="color: #800000;">getToken</span><span style="color: #800000;">"</span><span style="color: #000000;">)]
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span> Task&lt;<span style="color: #0000ff;">string</span>&gt; GetTokenAsync(<span style="color: #0000ff;">string</span> name, <span style="color: #0000ff;">string</span><span style="color: #000000;"> password)
    {
        </span><span style="color: #0000ff;">var</span> user = <span style="color: #0000ff;">await</span><span style="color: #000000;"> _userService.LoginAsync(name, password);
        </span><span style="color: #0000ff;">if</span> (user == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
            </span><span style="color: #0000ff;">return</span> <span style="color: #800000;">"</span><span style="color: #800000;">Login Failed</span><span style="color: #800000;">"</span><span style="color: #000000;">;

        </span><span style="color: #0000ff;">var</span> token =<span style="color: #000000;"> _tokenService.GetToken(user);
        </span><span style="color: #0000ff;">var</span> response = <span style="color: #0000ff;">new</span><span style="color: #000000;">
        {
            Status </span>= <span style="color: #0000ff;">true</span><span style="color: #000000;">,
            Token </span>=<span style="color: #000000;"> token,
            Type </span>= <span style="color: #800000;">"</span><span style="color: #800000;">Bearer</span><span style="color: #800000;">"</span><span style="color: #000000;">
        };
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> JsonConvert.SerializeObject(response);
    }
}</span></pre> 

   随后,我们又在项目配置文件中填写了几个字段,相关备注已注释,但值得说明的是有位朋友问我,服务器端生成的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;
    </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> AuthMiddleware(RequestDelegate next)
    {
        _next </span>=<span style="color: #000000;"> next;
    }
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">async</span><span style="color: #000000;"> Task Invoke(HttpContext httpContext)
    {
        </span><span style="color: #0000ff;">var</span> result = <span style="color: #0000ff;">await</span><span style="color: #000000;"> httpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
        </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">result.Succeeded)
        {
            httpContext.Response.StatusCode </span>= (<span style="color: #0000ff;">int</span><span style="color: #000000;">)HttpStatusCode.Unauthorized;
            </span><span style="color: #0000ff;">await</span> httpContext.Response.WriteAsync(<span style="color: #800000;">"</span><span style="color: #800000;">Authorize error</span><span style="color: #800000;">"</span><span style="color: #000000;">);
        }
        </span><span style="color: #0000ff;">else</span><span style="color: #000000;">
        {
            httpContext.User </span>=<span style="color: #000000;"> result.Principal;
            </span><span style="color: #0000ff;">await</span><span style="color: #000000;"> _next.Invoke(httpContext);
        }
    }
}</span></pre> 

   当然你也得在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 </span>=&gt;<span style="color: #000000;">
        {
            options.AddPolicy(</span><span style="color: #800000;">"</span><span style="color: #800000;">any</span><span style="color: #800000;">"</span>, builder =&gt;<span style="color: #000000;">
            {
                builder.AllowAnyOrigin() </span><span style="color: #008000;">//</span><span style="color: #008000;">允许任何来源的主机访问</span>

.AllowAnyMethod() .AllowAnyHeader();

            });
        });

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
           .AddJwtBearer(options </span>=&gt;<span style="color: #000000;">
           {
               options.TokenValidationParameters </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> TokenValidationParameters
               {
                   ValidIssuer </span>=<span style="color: #000000;"> jwtSetting.Issuer,
                   ValidAudience </span>=<span style="color: #000000;"> jwtSetting.Audience,
                   IssuerSigningKey </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
                   默认 300s
                   ClockSkew </span>=<span style="color: #000000;"> TimeSpan.Zero
               };
           });
        services.AddControllers();
    }</span></pre> 

   随后,你就可以编写带需认证才可以访问的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");
        </span><span style="color: #0000ff;">return</span> nameId != <span style="color: #0000ff;">null</span> ? Convert.ToInt32(nameId.Value) : <span style="color: #800080;">0</span><span style="color: #000000;">;
    }
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span><span style="color: #000000;"> GetUserName()
    {
        </span><span style="color: #0000ff;">return</span> _context.HttpContext.User.FindFirst(<span style="color: #800000;">"</span><span style="color: #800000;">name</span><span style="color: #800000;">"</span>)?<span style="color: #000000;">.Value;
    }
}</span></pre> 

  在源码中该类的定义如下,实际上我们可以看到只不过是判断了当前的http上下文吧,所以我们得出,如果认证失败,上下本信息也是空的。

public class HttpContextAccessor : IHttpContextAccessor
    {
        private static AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
    </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> HttpContext HttpContext
    {
        </span><span style="color: #0000ff;">get</span><span style="color: #000000;">
        {
            </span><span style="color: #0000ff;">return</span>  _httpContextCurrent.Value?<span style="color: #000000;">.Context;
        }
        </span><span style="color: #0000ff;">set</span><span style="color: #000000;">
        {
            </span><span style="color: #0000ff;">var</span> holder =<span style="color: #000000;"> _httpContextCurrent.Value;
            </span><span style="color: #0000ff;">if</span> (holder != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
            {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> Clear current HttpContext trapped in the AsyncLocals, as its done.</span>
                holder.Context = <span style="color: #0000ff;">null</span><span style="color: #000000;">;
            }

            </span><span style="color: #0000ff;">if</span> (value != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
            {
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> Use an object indirection to hold the HttpContext in the AsyncLocal,
                </span><span style="color: #008000;">//</span><span style="color: #008000;"> so it can be cleared in all ExecutionContexts when its cleared.</span>
                _httpContextCurrent.Value = <span style="color: #0000ff;">new</span> HttpContextHolder { Context =<span style="color: #000000;"> value };
            }
        }
    }

    </span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> HttpContextHolder
    {
        </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> HttpContext Context;
    }
}</span></pre> 

  如果要通过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

全部评论: 0

    我有话说: