首页 > 技术文章 > .Net Core 2.0 preview1实现自定义认证方案

zonciu 2017-07-09 12:25 原文

Microsoft.Authentication的使用方法在2.0中发生了比较大的变化,在1.1中认证配置是在Configure中完成。

public void ConfigureServices(IServiceCollection services)
{
  services.AddAuthentication();
}

public void Configure(IApplicationBuilder app)
{
  app.UseJwtBearerAuthentication(new JwtBearerOptions
  {
  Authority = Configuration["jwt:authority"],
  Audience = Configuration["jwt:audience"],
  Events = new JwtBearerEvents()
  {
    OnAuthenticationFailed = c =>
    {
      c.HandleResponse();
      c.Response.StatusCode = 500;
      c.Response.ContentType = "text/plain";
      if (Environment.IsDevelopment())
      {
        return c.Response.WriteAsync(c.Exception.ToString());
      }
      return c.Response.WriteAsync("An error occurred processing your authentication.");
    }  
  }
});

UseJwtBearerAuthentication其实是添加了一个中间件

        public static IApplicationBuilder UseJwtBearerAuthentication(this IApplicationBuilder app, JwtBearerOptions options)
        {
            if (app == null)
            {
                throw new ArgumentNullException(nameof(app));
            }
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            return app.UseMiddleware<JwtBearerMiddleware>(Options.Create(options));
        }

 

而在2.0中,认证配置则是在ConfigureServices中完成,并且通过Scheme-Handler的形式来实现多种认证方案的策略式选择。

public void ConfigureServices(IServiceCollection services)
{
  services.AddJwtBearerAuthentication(o =>
  {
    o.Authority = Configuration["jwt:authority"];
    o.Audience = Configuration["jwt:audience"];
    o.Events = new JwtBearerEvents()
    {
      OnAuthenticationFailed = c =>
      {
              c.HandleResponse();
              c.Response.StatusCode = 500;
              c.Response.ContentType = "text/plain";
              if (Environment.IsDevelopment())
              {
                  return c.Response.WriteAsync(c.Exception.ToString());
              }
              return c.Response.WriteAsync("An error occurred processing your authentication.");
          }
       };
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseAuthentication();
}  

 

public static IServiceCollection AddJwtBearerAuthentication(this IServiceCollection services, string authenticationScheme, Action<JwtBearerOptions> configureOptions)
{
    return services.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, configureOptions);
}

 

public static IApplicationBuilder UseAuthentication(this IApplicationBuilder app)
{
  if (app == null)
   {
     throw new ArgumentNullException(nameof(app));
   }         
   return app.UseMiddleware<AuthenticationMiddleware>();
}

 

namespace Microsoft.AspNetCore.Authentication
{
    public class AuthenticationMiddleware
    {
        private readonly RequestDelegate _next;

        public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }
            if (schemes == null)
            {
                throw new ArgumentNullException(nameof(schemes));
            }

            _next = next;
            Schemes = schemes;
        }

        public IAuthenticationSchemeProvider Schemes { get; set; }

        public async Task Invoke(HttpContext context)
        {
            context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
            {
                OriginalPath = context.Request.Path,
                OriginalPathBase = context.Request.PathBase
            });

            // REVIEW: alternatively could depend on a routing middleware to do this

            // Give any IAuthenticationRequestHandler schemes a chance to handle the request
            var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
            {
                var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
                if (handler != null && await handler.HandleRequestAsync())
                {
                    return;
                }
            }

            var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
            if (defaultAuthenticate != null)
            {
                var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
                if (result?.Principal != null)
                {
                    context.User = result.Principal;
                }
            }

            await _next(context);
        }
    }
}

  

也就是说,1.1的时候我们使用不同的认证方案时,是使用不同的中间件来实现认证,而2.0则刚好反过来,官方实现了一个统一的认证中间件,在中间件里获取对应的Scheme的Handler,然后调用Handler来完成认证过程。

在2.0中实现自己的认证方案非常方便——自己实现一个AuthenticationSchemeOptions和一个AuthenticationHandler,然后通过AddScheme注入并指定Scheme就可以了。

 

以官方JwtBearerAuthentication为例:

源码在此:https://github.com/aspnet/Security/tree/rel/2.0.0-preview1/src/Microsoft.AspNetCore.Authentication.JwtBearer

在ConfigureServices中调用AddJwtBearerAuthentication,其实是调用了AddScheme,authenticationScheme是JwtBearerDefaults.AuthenticationScheme。

JwtBearerOptions是继承AuthenticationSchemeOptions的类,用来保存认证配置。
JwtBearerHandler继承了AuthenticationHandler<JwtBearerOptions>,用于认证过程处理,需要什么依赖,直接从构造函数注入。关键在HandleAuthenticateAsync和HandleUnauthorizedAsync这两个方法。
 
认证流程是这样的:
1. ConfigureServices中调用AddScheme提供<AuthenticationSchemeOptions,AuthenticationHandler>并指定Scheme。
2. Configure中调用UseAuthentication。
3. 访问一个带有AuthorizeAttribute的Action。
4. AuthenticationMiddleware获取默认Scheme(或者AuthorizeAttribute指定的Scheme)的AuthenticationHandler,调用到Handler的HandleAuthenticateAsync,根据返回结果来决定是调用HandleUnauthorizedAsync还是HandleForbiddenAsync。
 
我们自己实现认证方案主要就是要实现HandleAuthenticateAsync这个方法,想怎么认证就怎么写。

推荐阅读