欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

asp.net core 使用identityServer4的密码模式来进行身份认证(2) 认证授权原理

程序员文章站 2022-07-11 09:39:43
前言:本文将会结合asp.net core 认证源码来分析起认证的原理与流程。asp.net core版本2.2 对于大部分使用asp.net core开发的人来说。 下面这几行代码应该很熟悉了。 废话不多说。直接看 app.UseAuthentication()的源码 现在来看看var defau ......

前言:本文将会结合asp.net core 认证源码来分析起认证的原理与流程。asp.net core版本2.2

对于大部分使用asp.net core开发的人来说。

下面这几行代码应该很熟悉了。

services.addauthentication(jwtbearerdefaults.authenticationscheme)
                .addjwtbearer(options =>
                {
                    options.requirehttpsmetadata = false;
                    options.audience = "sp_api";
                    options.authority = "http://localhost:5001";
                    options.savetoken = true;
                    
                })         
 app.useauthentication();

废话不多说。直接看 app.useauthentication()的源码

 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
            });

            // 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);
        }

现在来看看var defaultauthenticate = await schemes.getdefaultauthenticateschemeasync(); 干了什么。

在这之前。我们更应该要知道上面代码中  public iauthenticationschemeprovider schemes { get; set; } ,假如脑海中对这个iauthenticationschemeprovider类型的来源,有个清晰认识,对后面的理解会有很大的帮助

现在来揭秘iauthenticationschemeprovider 是从哪里来添加到ioc的。

  public static authenticationbuilder addauthentication(this iservicecollection services)
        {
            if (services == null)
            {
                throw new argumentnullexception(nameof(services));
            }

            services.addauthenticationcore();
            services.adddataprotection();
            services.addwebencoders();
            services.tryaddsingleton<isystemclock, systemclock>();
            return new authenticationbuilder(services);
        }

红色代码内部逻辑中就把iauthenticationschemeprovider添加到了ioc中。先来看看services.addauthenticationcore()的源码,这个源码的所在的解决方案的仓库地址是https://github.com/aspnet/httpabstractions,这个仓库目前已不再维护,其代码都转移到了asp.net core 仓库 。

下面为services.addauthenticationcore()的源码

 public static class authenticationcoreservicecollectionextensions
    {
        /// <summary>
        /// add core authentication services needed for <see cref="iauthenticationservice"/>.
        /// </summary>
        /// <param name="services">the <see cref="iservicecollection"/>.</param>
        /// <returns>the service collection.</returns>
        public static iservicecollection addauthenticationcore(this iservicecollection services)
        {
            if (services == null)
            {
                throw new argumentnullexception(nameof(services));
            }

            services.tryaddscoped<iauthenticationservice, authenticationservice>();
            services.tryaddsingleton<iclaimstransformation, noopclaimstransformation>(); // can be replaced with scoped ones that use dbcontext
            services.tryaddscoped<iauthenticationhandlerprovider, authenticationhandlerprovider>();
            services.tryaddsingleton<iauthenticationschemeprovider, authenticationschemeprovider>();
            return services;
        }

        /// <summary>
        /// add core authentication services needed for <see cref="iauthenticationservice"/>.
        /// </summary>
        /// <param name="services">the <see cref="iservicecollection"/>.</param>
        /// <param name="configureoptions">used to configure the <see cref="authenticationoptions"/>.</param>
        /// <returns>the service collection.</returns>
        public static iservicecollection addauthenticationcore(this iservicecollection services, action<authenticationoptions> configureoptions) {
            if (services == null)
            {
                throw new argumentnullexception(nameof(services));
            }

            if (configureoptions == null)
            {
                throw new argumentnullexception(nameof(configureoptions));
            }

            services.addauthenticationcore();
            services.configure(configureoptions);
            return services;
        }
    }

完全就可以看待添加了一个全局单例的iauthenticationschemeprovider对象。现在让我们回到middleware中探究schemes.getdefaultauthenticateschemeasync(); 干了什么。光看方法的名字都能猜出就是获取的默认的认证策略。

进入到iauthenticationschemeprovider 实现的源码中,按我的经验,来看先不急看getdefaultauthenticateschemeasync()里面的内部逻辑。必须的看下iauthenticationschemeprovider实现类的构造函数。它的实现类是authenticationschemeprovider。

先看看authenticationschemeprovider的构造方法

 public class authenticationschemeprovider : iauthenticationschemeprovider
    {
        /// <summary>
        /// creates an instance of <see cref="authenticationschemeprovider"/>
        /// using the specified <paramref name="options"/>,
        /// </summary>
        /// <param name="options">the <see cref="authenticationoptions"/> options.</param>
        public authenticationschemeprovider(ioptions<authenticationoptions> options)
            : this(options, new dictionary<string, authenticationscheme>(stringcomparer.ordinal))
        {
        }

        /// <summary>
        /// creates an instance of <see cref="authenticationschemeprovider"/>
        /// using the specified <paramref name="options"/> and <paramref name="schemes"/>.
        /// </summary>
        /// <param name="options">the <see cref="authenticationoptions"/> options.</param>
        /// <param name="schemes">the dictionary used to store authentication schemes.</param>
        protected authenticationschemeprovider(ioptions<authenticationoptions> options, idictionary<string, authenticationscheme> schemes)
        {
            _options = options.value;

            _schemes = schemes ?? throw new argumentnullexception(nameof(schemes));
            _requesthandlers = new list<authenticationscheme>();

            foreach (var builder in _options.schemes)
            {
                var scheme = builder.build();
                addscheme(scheme);
            }
        }

        private readonly authenticationoptions _options;
        private readonly object _lock = new object();

        private readonly idictionary<string, authenticationscheme> _schemes;
        private readonly list<authenticationscheme> _requesthandlers;

不难看出,上面的构造方法需要一个ioptions<authenticationoptions> 类型。没有这个类型,而这个类型是从哪里的了?

答:不知到各位是否记得addjwtbearer这个方法,再找个方法里面就注入了authenticationoptions找个类型。

看源码把

 public static class jwtbearerextensions
    {
        public static authenticationbuilder addjwtbearer(this authenticationbuilder builder)
            => builder.addjwtbearer(jwtbearerdefaults.authenticationscheme, _ => { });

        public static authenticationbuilder addjwtbearer(this authenticationbuilder builder, action<jwtbeareroptions> configureoptions)
            => builder.addjwtbearer(jwtbearerdefaults.authenticationscheme, configureoptions);

        public static authenticationbuilder addjwtbearer(this authenticationbuilder builder, string authenticationscheme, action<jwtbeareroptions> configureoptions)
            => builder.addjwtbearer(authenticationscheme, displayname: null, configureoptions: configureoptions);

        public static authenticationbuilder addjwtbearer(this authenticationbuilder builder, string authenticationscheme, string displayname, action<jwtbeareroptions> configureoptions)
        {
            builder.services.tryaddenumerable(servicedescriptor.singleton<ipostconfigureoptions<jwtbeareroptions>, jwtbearerpostconfigureoptions>());
            return builder.addscheme<jwtbeareroptions, jwtbearerhandler>(authenticationscheme, displayname, configureoptions);
        }
    }

不难通过上述代码看出它是及一个基于authenticationbuilder的扩展方法,而注入authenticationoptions的关键就在于 builder.addscheme<jwtbeareroptions, jwtbearerhandler>(authenticationscheme, displayname, configureoptions);  这行代码,按下f12看下源码

 public virtual authenticationbuilder addscheme<toptions, thandler>(string authenticationscheme, string displayname, action<toptions> configureoptions)
            where toptions : authenticationschemeoptions, new()
            where thandler : authenticationhandler<toptions>
            => addschemehelper<toptions, thandler>(authenticationscheme, displayname, configureoptions);    
private authenticationbuilder addschemehelper<toptions, thandler>(string authenticationscheme, string displayname, action<toptions> configureoptions)
            where toptions : class, new()
            where thandler : class, iauthenticationhandler
        {
            services.configure<authenticationoptions>(o =>
            {
                o.addscheme(authenticationscheme, scheme => {
                    scheme.handlertype = typeof(thandler);
                    scheme.displayname = displayname;
                });
            });
            if (configureoptions != null)
            {
                services.configure(authenticationscheme, configureoptions);
            }
            services.addtransient<thandler>();
            return this;
        }

照旧还是分为2个方法来进行调用,其重点就是addschemehelper找个方法。其里面配置authenticationoptions类型。现在我们已经知道了iauthenticationschemeprovider何使注入的。还由authenticationschemeprovider构造方法中ioptions<authenticationoptions> options是何使配置的,这样我们就对于认证有了一个初步的认识。现在可以知道对于认证中间件,必须要有一个iauthenticationschemeprovider 类型。而这个iauthenticationschemeprovider的实现类的构造函数必须要由ioptions<authenticationoptions> options,没有这两个类型,认证中间件应该是不会工作的。

回到认证中间件中。继续看var defaultauthenticate = await schemes.getdefaultauthenticateschemeasync();这句代码,源码如下

  public virtual task<authenticationscheme> getdefaultauthenticateschemeasync()
            => _options.defaultauthenticatescheme != null
            ? getschemeasync(_options.defaultauthenticatescheme)
            : getdefaultschemeasync();


 public virtual task<authenticationscheme> getschemeasync(string name)
            => task.fromresult(_schemes.containskey(name) ? _schemes[name] : null);
  private task<authenticationscheme> getdefaultschemeasync()
            => _options.defaultscheme != null
            ? getschemeasync(_options.defaultscheme)
: task.fromresult<authenticationscheme>(null);

 让我们先验证下方法1的三元表达式,应该执行那边呢?通过前面的代码我们知道authenticationoptions是在authenticationbuilder类型的addschemehelper方法里面进行配置的。经过我的调试,发现方法1会走右边。其实最终还是从一个字典中取到了默认的authenticationscheme对象。到这里中间件的里面var defaultauthenticate = await schemes.getdefaultauthenticateschemeasync();代码就完了。最终就那到了authenticationscheme的对象。

下面来看看 中间件中var result = await context.authenticateasync(defaultauthenticate.name);这句代码干了什么。按下f12发现是一个扩展方法,还是到httpabstractions解决方案里面找下源码

源码如下

 public static task<authenticateresult> authenticateasync(this httpcontext context, string scheme) =>
            context.requestservices.getrequiredservice<iauthenticationservice>().authenticateasync(context, scheme);

通过上面的方法,发现是通过iauthenticationservice的authenticateasync() 来进行认证的。那么现在iauthenticationservice这个类是干什么 呢?

下面为iauthenticationservice的定义

 public interface iauthenticationservice
    {
               task<authenticateresult> authenticateasync(httpcontext context, string scheme);

               task challengeasync(httpcontext context, string scheme, authenticationproperties properties);

               task forbidasync(httpcontext context, string scheme, authenticationproperties properties);

               task signinasync(httpcontext context, string scheme, claimsprincipal principal, authenticationproperties properties);

                task signoutasync(httpcontext context, string scheme, authenticationproperties properties);
    }

 iauthenticationservice的authenticateasync()方法的实现源码

public class authenticationservice : iauthenticationservice
    {
        /// <summary>
        /// constructor.
        /// </summary>
        /// <param name="schemes">the <see cref="iauthenticationschemeprovider"/>.</param>
        /// <param name="handlers">the <see cref="iauthenticationrequesthandler"/>.</param>
        /// <param name="transform">the <see cref="iclaimstransformation"/>.</param>
        public authenticationservice(iauthenticationschemeprovider schemes, iauthenticationhandlerprovider handlers, iclaimstransformation transform)
        {
            schemes = schemes;
            handlers = handlers;
            transform = transform;
        }
 public virtual async task<authenticateresult> authenticateasync(httpcontext context, string scheme)
        {
            if (scheme == null)
            {
                var defaultscheme = await schemes.getdefaultauthenticateschemeasync();
                scheme = defaultscheme?.name;
                if (scheme == null)
                {
                    throw new invalidoperationexception($"no authenticationscheme was specified, and there was no defaultauthenticatescheme found.");
                }
            }

            var handler = await handlers.gethandlerasync(context, scheme);
            if (handler == null)
            {
                throw await createmissinghandlerexception(scheme);
            }

            var result = await handler.authenticateasync();
            if (result != null && result.succeeded)
            {
                var transformed = await transform.transformasync(result.principal);
                return authenticateresult.success(new authenticationticket(transformed, result.properties, result.ticket.authenticationscheme));
            }
            return result;
        }
 

 通过构造方法可以看到这个类的构造方法需要iauthenticationschemeprovider类型和iauthenticationhandlerprovider 类型,前面已经了解了iauthenticationschemeprovider是干什么的,取到配置的授权策略的名称,那现在iauthenticationhandlerprovider 是干什么的,看名字感觉应该是取到具体授权策略的handler.废话补多少,看iauthenticationhandlerprovider 接口定义把

 public interface iauthenticationhandlerprovider
    {
        /// <summary>
        /// returns the handler instance that will be used.
        /// </summary>
        /// <param name="context">the context.</param>
        /// <param name="authenticationscheme">the name of the authentication scheme being handled.</param>
        /// <returns>the handler instance.</returns>
        task<iauthenticationhandler> gethandlerasync(httpcontext context, string authenticationscheme);
    }

通过上面的源码,跟我猜想的不错,果然就是取得具体的授权策略

现在我就可以知道authenticationservice是对iauthenticationschemeprovider和iauthenticationhandlerprovider封装。最终调用iauthentionhandel的authenticateasync()方法进行认证。最终返回一个authenticateresult对象。

总结,对于asp.net core的认证来水,他需要下面这几个对象

authenticationbuilder      扶着对认证策略的配置与初始话

iauthenticationhandlerprovider authenticationhandlerprovider 负责获取配置了的认证策略的名称

iauthenticationschemeprovider authenticationschemeprovider 负责获取具体认证策略的handle

iauthenticationservice authenticationservice 实对上面两个provider 的封装,来提供一个具体处理认证的入口

iauthenticationhandler 和的实现类,是以哦那个来处理具体的认证的,对不同认证策略的出来,全是依靠的它的authenticateasync()方法。

authenticateresult  最终的认证结果。

哎写的太垃圾了。