Asp.net core 如何在ASP Net Core 2.0 MVC web app和Azure AD之间强制重新验证

Asp.net core 如何在ASP Net Core 2.0 MVC web app和Azure AD之间强制重新验证,asp.net-core,asp.net-core-mvc,azure-active-directory,asp.net-core-2.0,azure-authentication,Asp.net Core,Asp.net Core Mvc,Azure Active Directory,Asp.net Core 2.0,Azure Authentication,我有一个ASP.Net核心MVC web应用程序,它使用Azure AD进行身份验证。我刚刚收到一个新的要求,要求用户在输入一些敏感信息之前重新验证(输入这些新信息的按钮调用一个控制器操作,初始化一个新的视图模型,并将部分视图返回到引导模式) 我遵循了这篇文章,它为实现这一要求提供了很好的指南。我不得不做一些调整,以使它与ASP.NETCore2.0的工作,我认为这是正确的,但我的问题如下 将资源筛选器装饰“[RequireReauthentication(0)]”添加到我的控制器操作会起作用,

我有一个ASP.Net核心MVC web应用程序,它使用Azure AD进行身份验证。我刚刚收到一个新的要求,要求用户在输入一些敏感信息之前重新验证(输入这些新信息的按钮调用一个控制器操作,初始化一个新的视图模型,并将部分视图返回到引导模式)

我遵循了这篇文章,它为实现这一要求提供了很好的指南。我不得不做一些调整,以使它与ASP.NETCore2.0的工作,我认为这是正确的,但我的问题如下

  • 将资源筛选器装饰“[RequireReauthentication(0)]”添加到我的控制器操作会起作用,但是传递值0意味着代码永远不会到达筛选器内的wait.next()命令。如果我将参数值改为30,它可以工作,但看起来非常随意。这个值应该是多少

  • 当调用返回完整视图的控制器操作时,重新验证有效。但是,当我从ajax请求调用操作,将一个部分返回到引导模式时,它会在加载模式之前失败

  • 对飞行前请求的响应未通过访问控制检查:否 “Access Control Allow Origin”标头出现在请求的服务器上 资源。因此,不允许使用源“” 通路

    这看起来像是一个CORS问题,但我不知道为什么在执行标准mvc过程时,而不是从jquery调用时,它会起作用。添加

    services.AddCors()

    app.UseCors(builder=> 建造商。带原产地(“”)

    对我的启动文件没有任何影响。这里可能有什么问题

    Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // Ommitted for clarity...
    
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddAzureAd(options => Configuration.Bind("AzureAd", options))
        .AddCookie();
    
        services.AddCors();
    
        // Ommitted for clarity...
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // Ommitted for clarity...
    
        app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));
    
        app.UseStaticFiles();
    
        app.UseAuthentication();
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    
    public static class AzureAdAuthenticationBuilderExtensions
    {        
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
            => builder.AddAzureAd(_ => { });
    
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
            builder.AddOpenIdConnect(options =>
            {
                options.ClaimActions.Remove("auth_time");
                options.Events = new OpenIdConnectEvents
                {
                    OnRedirectToIdentityProvider = RedirectToIdentityProvider
                };
            });
            return builder;
        }
    
        private static Task RedirectToIdentityProvider(RedirectContext context)
        {
            // Force reauthentication for sensitive data if required
            if (context.ShouldReauthenticate())
            {
                context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
            }
            else
            {
                context.Properties.RedirectUri = new PathString("/Account/SignedIn");
            }
    
            return Task.FromResult(0);
        }
    
        internal static bool ShouldReauthenticate(this RedirectContext context)
        {
            context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
            bool shouldReauthenticate = false;
    
            if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
            {
                throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
            }
    
            return shouldReauthenticate;
        }
    
        // Ommitted for clarity...
    }
    
    public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
    {
        private int _timeElapsedSinceLast;
        public RequireReauthenticationAttribute(int timeElapsedSinceLast)
        {
            _timeElapsedSinceLast = timeElapsedSinceLast;
        }
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
            var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    
            if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
            {
                await next();
            }
            else
            {
                var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
                await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
            }
        }
    }
    
    [HttpGet]
    [RequireReauthentication(0)]
    public IActionResult CreateNote(int id)
    {
        TempData["IsCreate"] = true;
        ViewData["PostAction"] = "CreateNote";
        ViewData["PostRouteId"] = id;
        var model = new NoteViewModel
        {
            ClientId = id
        };
        return PartialView("_Note", model);
    }
    
    azureadaauthenticationbuilderextensions.cs

    public void ConfigureServices(IServiceCollection services)
    {
        // Ommitted for clarity...
    
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddAzureAd(options => Configuration.Bind("AzureAd", options))
        .AddCookie();
    
        services.AddCors();
    
        // Ommitted for clarity...
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // Ommitted for clarity...
    
        app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));
    
        app.UseStaticFiles();
    
        app.UseAuthentication();
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    
    public static class AzureAdAuthenticationBuilderExtensions
    {        
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
            => builder.AddAzureAd(_ => { });
    
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
            builder.AddOpenIdConnect(options =>
            {
                options.ClaimActions.Remove("auth_time");
                options.Events = new OpenIdConnectEvents
                {
                    OnRedirectToIdentityProvider = RedirectToIdentityProvider
                };
            });
            return builder;
        }
    
        private static Task RedirectToIdentityProvider(RedirectContext context)
        {
            // Force reauthentication for sensitive data if required
            if (context.ShouldReauthenticate())
            {
                context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
            }
            else
            {
                context.Properties.RedirectUri = new PathString("/Account/SignedIn");
            }
    
            return Task.FromResult(0);
        }
    
        internal static bool ShouldReauthenticate(this RedirectContext context)
        {
            context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
            bool shouldReauthenticate = false;
    
            if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
            {
                throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
            }
    
            return shouldReauthenticate;
        }
    
        // Ommitted for clarity...
    }
    
    public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
    {
        private int _timeElapsedSinceLast;
        public RequireReauthenticationAttribute(int timeElapsedSinceLast)
        {
            _timeElapsedSinceLast = timeElapsedSinceLast;
        }
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
            var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    
            if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
            {
                await next();
            }
            else
            {
                var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
                await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
            }
        }
    }
    
    [HttpGet]
    [RequireReauthentication(0)]
    public IActionResult CreateNote(int id)
    {
        TempData["IsCreate"] = true;
        ViewData["PostAction"] = "CreateNote";
        ViewData["PostRouteId"] = id;
        var model = new NoteViewModel
        {
            ClientId = id
        };
        return PartialView("_Note", model);
    }
    
    Razor视图(片段)

    
    

    谢谢你的帮助。谢谢

    CORS问题不在你的应用程序中。 您的AJAX调用正在尝试按照身份验证重定向到Azure AD, 这是行不通的

    相反,您可以在
    RedirectToIdentityProvider
    函数中检查请求是否是AJAX请求。 如果是,则使其返回401状态代码,无重定向


    然后,您的客户端JS需要检测状态代码,并发出一个重定向来触发身份验证。

    谢谢,但我有点不确定如何实施您的建议,所以我将在本周末试一试,让您知道。我刚刚尝试迁移到Core2.1,而新的mvc模板不再有任何AzureAD扩展或account controller,因此我不确定这一切在将来会如何工作,但我期待着你下一篇关于这个主题的博客。