当前位置: 移动技术网 > IT编程>开发语言>.net > asp.net core 使用identityServer4的密码模式来进行身份认证(2) 认证授权原理

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

2019年03月14日  | 移动技术网IT编程  | 我要评论

前言:本文将会结合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  最终的认证结果。

哎写的太垃圾了。

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网