前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.Net 5.0 通过IdentityServer4结合认证授权、路由终结点、OpenIdConnect组件实现单点登录源码解析

.Net 5.0 通过IdentityServer4结合认证授权、路由终结点、OpenIdConnect组件实现单点登录源码解析

作者头像
郑小超.
发布2022-06-15 19:10:58
4440
发布2022-06-15 19:10:58
举报
文章被收录于专栏:GreenLeavesGreenLeaves

1、本文主要介绍.Net 5.0通过认证授权、路由终结点、OpenIdConnect组件结合IdentityServer4实现单点登录的源码解析,内容较多,只解读demo的调用部分.

首页看调用代码:

    public class Startup
    {
       
        public void ConfigureServices(IServiceCollection services)
        {
            
            services.AddControllersWithViews();
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = "http://localhost:5001";
                options.RequireHttpsMetadata = false;
                options.ClientId = "mvc";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.SaveTokens = true;
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute()
                    .RequireAuthorization();
            });
        }
    }

首先看ConfigureServices方法,通过该方法注入将控制器注入到了容器中,并配置认证组件的默认认证方案为Cookie,Challenge方案设置为oidc,其实就是当用户未认证时,回调用oidc方法的Handler,下面会解释,接着看Configure方法,启用路由、认证、授权、终结点组件,并在终结点里面配置了给默认控制器路由全都设置了Authorize特性.相当于所有的控制器方法,必要要登录过后才能访问.下面会进行源码解析.

2、授权中间件源码解析

再通过上述代码配置好客户端之后,说明客户端已经具备接入oidc服务端了(本文不多做讲解),那么现在访问客户端api,必然会被拦截,应为在配置客户端时,引入了授权组件,并且给所有的控制器方法加上了Authorize特性.相当于所有的控制器方法,必要要登录过后才能访问.ok,带着这个前提条件,来看看授权中间件(如果不了解授权中间件请参阅.Net Core 3.0授权组件源码解析)做了什么.

            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var endpoint = context.GetEndpoint();
            if (endpoint != null)
            {
                
            }

            // IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter
            var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();

首先先在当前访问的终结点获取到其Metadata中关于授权的信息细节如下:

    public interface IAuthorizeData
    {
        /// <summary>
        /// 终结点(可以理解为控制器方法)配置的认证策略
        /// </summary>
        string? Policy { get; set; }

        /// <summary>
        /// 终结点(可以理解为控制器方法)配置的角色(大多数系统是基于Role的授权策略)
        /// </summary>
        string? Roles { get; set; }

        /// <summary>
        /// 终结点(可以理解为控制器方法)配置的认证方式
        /// </summary>
        string? AuthenticationSchemes { get; set; }
    }

细节就是获取控制器方法上的实现IAuthorizeData(默认Authorize特性)的特性内容,包括方法采用的认证方案、采用的自定义授权策略、采用的角色授权策略信息.获取当上述信息后,接下看源码如下:

            var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
            if (policy == null)
            {
                await _next(context);
                return;
            }

判断一下控制器方法上的Authorize特性中的授权策略相关的内容是否为空,为空的话,直接执行接下去的中间件.这里查阅下授权策略是如何Combine的,代码如下:

        public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
        {
            if (policyProvider == null)
            {
                throw new ArgumentNullException(nameof(policyProvider));
            }

            if (authorizeData == null)
            {
                throw new ArgumentNullException(nameof(authorizeData));
            }

            // Avoid allocating enumerator if the data is known to be empty
            var skipEnumeratingData = false;
            if (authorizeData is IList<IAuthorizeData> dataList)
            {
                skipEnumeratingData = dataList.Count == 0;
            }

            AuthorizationPolicyBuilder? policyBuilder = null;
            if (!skipEnumeratingData)
            {
                foreach (var authorizeDatum in authorizeData)
                {
                    if (policyBuilder == null)
                    {
                        policyBuilder = new AuthorizationPolicyBuilder();
                    }

                    var useDefaultPolicy = true;
                    if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
                    {
                        var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
                        if (policy == null)
                        {
                            throw new InvalidOperationException("");
                        }
                        policyBuilder.Combine(policy);
                        useDefaultPolicy = false;
                    }

                    var rolesSplit = authorizeDatum.Roles?.Split(',');
                    if (rolesSplit?.Length > 0)
                    {
                        var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
                        policyBuilder.RequireRole(trimmedRolesSplit);
                        useDefaultPolicy = false;
                    }

                    var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
                    if (authTypesSplit?.Length > 0)
                    {
                        foreach (var authType in authTypesSplit)
                        {
                            if (!string.IsNullOrWhiteSpace(authType))
                            {
                                policyBuilder.AuthenticationSchemes.Add(authType.Trim());
                            }
                        }
                    }

                    if (useDefaultPolicy)
                    {
                        policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
                    }
                }
            }

            // If we have no policy by now, use the fallback policy if we have one
            if (policyBuilder == null)
            {
                var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
                if (fallbackPolicy != null)
                {
                    return fallbackPolicy;
                }
            }

            return policyBuilder?.Build();
        }
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档