Asp.net 核心 Microsoft OIDC 库 UnAuthorized Code received 事件被触发两次

Posted

技术标签:

【中文标题】Asp.net 核心 Microsoft OIDC 库 UnAuthorized Code received 事件被触发两次【英文标题】:Asp.net core Microsoft OIDC library OnAuthorizedCode recieved event getting fired twice 【发布时间】:2021-12-18 18:47:43 【问题描述】:

我有一个 OIDC 应用程序,它给了我一段时间的相关错误。我们已经设法通过正确传递相关 cookie 来解决它。我们的基础架构具有以下网络拓扑。

Openshift Container(OIDC App) -> Reverser Proxy/Api gateway -> Identity Provider.

如果我们使用样板代码,回调路径会选择打开班次的主机,因此重定向无法正常工作。我们现在正在 onRedirectToIdentityProvider 事件上构建 URL。

我们还使用OnAuthorizationCodeReceived 事件来进行 /Token 调用,因为默认配置不起作用。

我们使用授权代码正确地得到回调,但我们看到 onAuthorizationCodeRecieved 被触发两次。这仅在具有反向代理设置的测试环境中发生。在开发框中,没有问题,但没有设置反向代理(现在,反向代理只是一个传递,所以不知道这会如何破坏事情)。

下面的代码片段

 public void ConfigureServices(IServiceCollection services)
        
            string _authorityAPI = Configuration.GetValue<string>("authority");
            string _org = Configuration.GetValue<string>("orgDomain");
            string clientId = Configuration.GetValue<string>("ClientId");
            string clientSecret = Configuration.GetValue<string>("ClientSecret");


            services.Configure<CookiePolicyOptions>(options =>
            
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
                options.Secure = CookieSecurePolicy.Always;
                options.HandleSameSiteCookieCompatibility();
           

            
            services.AddAuthentication(auth =>
            
                auth.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            )
            .AddCookie(options =>
            
                options.LoginPath = "/Profile/Index/";
                options.LogoutPath = "/Profile/Logout/";
            )
           
            .AddOpenIdConnect(options =>
            

                options.ClientId = clientId;
                options.ClientSecret = clientSecret;
                options.Authority = Configuration.GetValue<string>("Authority");
                options.CallbackPath = "/web/auth/callback";
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.MetadataAddress = string.Format("0/.well-known/openid-configuration", _authorityAPI);
                options.TokenValidationParameters.ValidateIssuer = false;
                //options.GetClaimsFromUserInfoEndpoint = true;
                options.RequireHttpsMetadata = true;
                options.SaveTokens = true;
                options.Scope.Add("openid");
                options.Scope.Add("email");
                options.Scope.Add("profile");
                options.NonceCookie.SameSite = SameSiteMode.None;
                options.CorrelationCookie.SameSite = SameSiteMode.None;
                options.NonceCookie.Path = "/";
                options.CorrelationCookie.Path = "/";
                
                options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new ApiGatewayRetriever(_authorityAPI, _org));
                options.Events = new OpenIdConnectEvents()
                
                    OnRedirectToIdentityProvider = (context) =>
                    
                        if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                        
                           
                                context.ProtocolMessage.Parameters.Clear();
                                context.ProtocolMessage.IssuerAddress = centralLogin + "?RedirectURL=https://" + GetRequestHostName(context, Configuration);
                                return Task.FromResult(0);
                            
                        
                        
                        return Task.FromResult(0);
                    
                    ,
                    OnAuthorizationCodeReceived = authorizationCtx =>
                    
                        try
                        
                            BellLogger.WriteLog("OnAuthorizationCodeReceived start:" + authorizationCtx.TokenEndpointRequest.Code, Framework.Common.LogType.Info);

                            HttpClient httpClient = new HttpClient();
                            TokenClientOptions tokenClientOptions = new TokenClientOptions()
                            
                                ClientId = clientId,
                                ClientSecret = clientSecret,
                                Address = string.Format("0/v1/token", _authorityAPI)
                            ;
                            var tokenClient = new TokenClient(httpClient, tokenClientOptions);

                            var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(authorizationCtx.TokenEndpointRequest.Code, authorizationCtx.TokenEndpointRequest.RedirectUri).Result;
                    authorizationCtx.HandleCodeRedemption(tokenResponse.AccessToken, tokenResponse.IdentityToken);

                           
                        
                        catch (Exception ex)
                        
                            Logger.WriteLog(ex, Framework.Common.LogType.Error);
                        

                        return Task.FromResult(0);
                    
                ;

            );
            services.AddSession(options =>
            
                options.Cookie.Name = ".lLogin.Session";
                options.IdleTimeout = TimeSpan.FromSeconds(10);
                options.Cookie.IsEssential = true;
            );
            services.ConfigureApplicationCookie(options =>
            
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
            );
            services.Configure<ForwardedHeadersOptions>(options =>
            
                options.ForwardedHeaders = ForwardedHeaders.All;
            );
            services.AddControllersWithViews();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        

    
        private void CheckSameSite(HttpContext httpContext, CookieOptions options)
        
            if (options.SameSite == SameSiteMode.None)
            
                var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
                // TODO: Use your User Agent library of choice here.
                if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6") || userAgent.Contains("Chrome/9"))
                
                    // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)
                    options.SameSite = SameSiteMode.Unspecified;
                
            
        


    
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            //app.UseAntiXssMiddleware();
            app.UseCookiePolicy();
            app.UseAuthentication();
            app.UseRouting();
            app.UseSession();
            app.UseAuthorization();
            app.UseForwardedHeaders();

            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "controller=Home/action=Index/id?");
            );
        

【问题讨论】:

【参考方案1】:

这可能不是上述问题的答案,但是我们解决了这个问题。

我们删除了所有事件的自定义实现,包括 onAuthorizationCodeReceived。

默认配置的问题是,当反向代理将调用路由到 OCP 环境时,主机标头从 OCP 开始添加。反向代理添加了正确主机的标头,然后一切都开始正常工作。主机标头是重要的值。

【讨论】:

【参考方案2】:

这可能是一个重定向问题,值得根据 URL 而不是 C# 代码来理解事物。对授权代码流的响应如下所示:

https://myapp.com?code=xxx&state=yyy

反向代理 - 或 Web 服务器 - 或 openid 提供者 - 可能期望响应具有(或不具有)尾部反斜杠。例如,第一个 URL 会导致重定向到第二个:

https://myapp.com?code=xxx&state=yyy https://myapp.com/?code=xxx&state=yyy

我会在OnAuthorizationCodeReceived 中添加一些日志语句,并且可能还会跟踪浏览器/HTTP 流量以查看是否正在发生此类事情。

【讨论】:

我的回调 url 中没有斜杠,反向代理也不期望它。这似乎不是问题。

以上是关于Asp.net 核心 Microsoft OIDC 库 UnAuthorized Code received 事件被触发两次的主要内容,如果未能解决你的问题,请参考以下文章

Asp.net Core OpenIdConnect (OIDC) 在哪里验证状态参数

ASP.NET Core分布式项目实战oauth2 + oidc 实现 server部分

从 ASP.NET Core MVC 项目提供 Angular SPA 时,我可以使用 OIDC 混合流吗?

ASP.NET 核心模型验证无法按预期工作

oidc-client登录后重定向

在 Asp Net Core 3.1 中手动验证来自 OIDC 提供程序的令牌,而没有“众所周知的”元数据