ASP.NET Core 3.1 中的条件访问拒绝路径

问题描述

我的项目有两个控制器来支持来自不同角色的用户 - 成员顾问登录时我设置了“角色”ClaimType 每个。

会员和顾问有不同的登录页面登录MemberControllerConsultantController重定向到“桌面”操作。

CONSULTANTCONTROLLER.CS

    [HttpPost()]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> SignIn(SignIn sin)
    {
        try
        {
            // check authorisation
            if (ModelState.IsValid)
            {
                sin = await RepoSamadhi.ShopSignIn(sin);
                if (sin.ShopID == 0 || sin.IsValidationFail || string.IsNullOrEmpty(sin.ShopToken))
                {
                    is_err = true;
                    _logger.Loginformation("Consultant SignIn Invalid Credentials",sin.EmailAddress);                        
                    ModelState.AddModelError("Consultant","Account not found. Check your credentials.");
                }
            }                
            else
            {
                sin.IsSignInFailed = true;
                return View("SignIn",sin);
            }

            // create claims
            var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Sid,sin.ShopToken),new Claim(ClaimTypes.NameIdentifier,sin.ShopID.ToString()),new Claim(ClaimTypes.Email,sin.EmailAddress.ToLower()),new Claim(ClaimTypes.Role,"Consultant")
        };

            // create identity
            var identity = new ClaimsIdentity(claims,CookieAuthenticationDefaults.AuthenticationScheme); // cookie or local            

            // create principal
            ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(claims,CookieAuthenticationDefaults.AuthenticationScheme));

            // create auth properties
            var authProperties = new AuthenticationProperties
            {
                IsPersistent = sin.RememberMe;
            };

            // sign-in
            await HttpContext.SignInAsync(scheme: CookieAuthenticationDefaults.AuthenticationScheme,principal: principal,properties: authProperties);
        }
        catch (Exception ex)
        {
            gFunc.ProcessError(ex);
        }
        return RedirectToAction("Desktop",new { date = DateTime.Today.ToString("d MMM yyyy"),timer = false });
    }

STARTUP.CS

    public void ConfigureServices(IServiceCollection services)
    {
        try
        {
            services.AddRazorPages()
                .AddRazorRuntimeCompilation();

            services.AddControllersWithViews();

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,options =>
                {
                    options.ExpireTimeSpan = new TimeSpan(30,0);
                    options.LoginPath = new PathString("/Home/Index/");
                    options.AccessDeniedpath = new PathString("/Home/Index/");
                    options.logoutPath = new PathString("/Home/Index/");
                    options.Validate();
                });

            services.Configure<Microsoft.AspNetCore.Identity.IdentityOptions>(options =>
            {
                options.Password.requiredigit = true;
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.requiredLength = 8;
                options.Password.requiredUniqueChars = 1;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.AllowedForNewUsers = true;
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMnopQRSTUVWXYZ0123456789-._@+";
                options.User.RequireUniqueEmail = false;
            });

            // add detection services container and device resolver service
            services.AddDetectionCore()
                .AddDevice();

            services.AddMvc();
            services.AddAntiforgery();
            services.Configure<Mvcoptions>(options =>
            {
                options.Filters.Add(new RequireHttpsAttribute());
            });
        }
        catch (Exception ex)
        {
            gFunc.ProcessError(ex);
        }
    }

问题

如何配置身份验证服务以在用户尝试访问授权资源但未登录(即没有有效的身份验证 cookie)时将用户重定向到正确的登录页面?目前我只有一个“AccessDeniedpath”,它会将用户带到主页。

解决方法

我尝试了 King King 的方法,通过自定义 CookieAuthenticationHandler 来覆盖 HandleForbiddenAsync,但代码永远不会执行。

这是因为尚未登录的用户“未经授权”。如果他们尝试访问 [Authorize] 资源,则会将用户定向到 LoginPath,而不是 AccessDeniedPath。这对应于 HTTP 请求的 401。

如果用户已经登录,则被“禁止”,但他们使用的身份无权查看请求的资源,这对应于 HTTP 中的 403。

在 MS 文档中:“AccessDeniedPath 获取或设置用户代理重定向到的可选路径,如果用户不批准远程服务器请求的授权请求。此属性不是由默认。在这种情况下,如果远程授权服务器返回 access_denied 响应,则会引发异常。"

因此,在登录并随后请求没有所需角色的受保护资源(即用 [Authorize(Roles = "MyRole")] 装饰的操作后,应重定向到配置的 AccessDeniedPath。在这种情况下,我应该能够使用King King的做法。

解决方案

最后,我只是向 CookieAuthenticationOptions 事件 (OnRedirectToLogin) 添加了一个委托。

我已经更新了以下代码以纳入来自 KingKing 的反馈/评论。这包括使用 StartsWithSegments 而不仅仅是 Path.ToString().Contains。

同样按照 KK 的建议,我捕获了默认回调,然后在返回中使用它。

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,o =>
{
     o.ExpireTimeSpan = new TimeSpan(90,0);
     o.AccessDeniedPath = new PathString("/Samadhi/SignIn/");
     o.LoginPath = new PathString("/Samadhi/SignIn/");
     o.LogoutPath = new PathString("/Samadhi/SignOut/");
     var defaultCallback = o.Events.OnRedirectToLogin;
     o.Events.OnRedirectToLogin = context =>
     {
          if (context.Request.Path.StartsWithSegments(new PathString("/member"),StringComparison.OrdinalIgnoreCase))
          {
               context.RedirectUri = "/Member/SignIn/";
               context.Response.Redirect(context.RedirectUri);
          }
          else if (context.Request.Path.StartsWithSegments(new PathString("/consultant"),StringComparison.OrdinalIgnoreCase))
          {
               context.RedirectUri = "/Consultant/SignIn/";
               context.Response.Redirect(context.RedirectUri);
          }
          return defaultCallback(context);
    };
    o.Validate();
});
,

在我看来,主要问题是如果用户不包含令牌,您如何知道当前登录用户?

我认为,我建议您首先使用主登录页面。然后如果用户输入了用户名,就可以使用 ajax 之类的 js 来检查服务器中的用户名或电子邮件。

如果用户是 Members ,那么你可以在 ajax success 方法中编写逻辑,将用户重定向到 Member 登录页面。