代码之家  ›  专栏  ›  技术社区  ›  McGuireV10

重新授权持久登录(MVC客户端),而不触发登录UI

  •  3
  • McGuireV10  · 技术社区  · 7 年前

    我试图弄清楚服务器端客户端(MVC/ASP.NET Core 2)如何查询IdentityServer4以检索在前一个会话中创建的持久登录的各种声明范围 不提示登录 如果永久登录无效(用户不活动、cookie过期等)。

    我们将隐式流与第三方身份验证(Google、FB等)结合使用,但我们在IdentityServer的 ExternalLoginCallback .

    访问上的声明 HttpContext.User (我们是 使用ASP。NET Identity)在建立登录的会话期间非常有效。在以后的某个会话中,导航到具有 [Authorize] 属性也起作用:如果用户以前登录过,则他们可以透明地访问资源,填充声明等。如果没有,则会提示他们登录,这对于用户启动的操作来说是正常的。

    然而,我们要求客户端登录页根据用户是匿名的还是经过身份验证的来更改内容。一个简单的例子是匿名用户的“注册”和“登录”链接,而经过身份验证的用户的“帐户”和“注销”链接。

    因此,我们有理由检索声明并在有效的情况下启动持久登录,但在无效的情况下什么也不做(没有登录提示):我们不希望登录页强制每个匿名用户登录屏幕。

    关于我们在管道两端的设置,没有什么特别要说的。客户:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    
    .AddOpenIdConnect("oidc", options =>
    {
        options.SignInScheme = "Cookies";
        options.Authority = "https://localhost:5000";
        options.RequireHttpsMetadata = true;
        options.ClientId = "example.com.webserver";
        options.ClientSecret = "examplesecret";
        options.ResponseType = "id_token";
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("example.com.identity");
    });
    

    IdentityServer客户端资源定义:

    new Client
    {
        ClientId = "example.com.webserver",
        ClientName = "example.com",
        ClientUri = "https://localhost:5002",
        AllowedGrantTypes = GrantTypes.Implicit,
        ClientSecrets = {new Secret("examplesecret".Sha256())},
        RequireConsent = false,
        AllowRememberConsent = true,
        AllowOfflineAccess = true,
        RedirectUris = { "https://localhost:5002/signin-oidc"},
        PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc"},
        AllowedScopes = new List<string>
        {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile,
            IdentityServerConstants.StandardScopes.Email,
            IdentityServerConstants.StandardScopes.Phone,
            IdentityServerConstants.StandardScopes.Address,
            "example.com.identity"
        }
    }
    

    客户端运行有意登录(用户单击“登录”链接),如下所示:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task Login()
    {
        await HttpContext.SignOutAsync("oidc");
        await HttpContext.ChallengeAsync("oidc", 
            new AuthenticationProperties() {
                RedirectUri = Url.Action("LoginCallback")
            });
    }
    
    2 回复  |  直到 7 年前
        1
  •  4
  •   McGuireV10    7 年前

    解决方案是在设置中添加第二个OIDC身份验证流,拦截重定向以更改 Prompt 选项到 none 以便不显示登录提示,截取结果 login_required 错误消息,并在登录页的 PageModel OnGet handler(客户端应用程序使用RazorPages)。

    一个警告是,处理程序必须设置标志,以便只尝试一次,并且可以检测页面是第一次被点击,还是作为登录尝试的返回行程。这只需将一个值放入剃须刀即可实现 TempData 它只是一个基于cookie的名称-值对存储桶。

    添加到安装程序。反恐精英

    .AddOpenIdConnect("persistent", options =>
    {
        options.CallbackPath = "/signin-persistent";
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                context.ProtocolMessage.Prompt = "none";
                return Task.FromResult<object>(null);
            },
    
            OnMessageReceived = context => {
                if(string.Equals(context.ProtocolMessage.Error, "login_required", StringComparison.Ordinal))
                {
                    context.HandleResponse();
                    context.Response.Redirect("/");
                }
                return Task.FromResult<object>(null);
            }
        };
    
        options.SignInScheme = "Cookies";
        options.Authority = "https://localhost:5000";
        options.RequireHttpsMetadata = true;
        options.ClientId = "example.com.webserver";
        options.ClientSecret = "examplesecret";
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("example.com.identity");
    })
    

    登录页(Index.cshtml.cs)

    public class IndexModel : PageModel
    {
        private bool PersistentLoginAttempted = false;
        private const string PersistentLoginFlag = "persistent_login_attempt";
    
        public IActionResult OnGet()
        {
            // Always clean up an existing flag.
            bool FlagFound = false;
            if(!String.IsNullOrEmpty(TempData[PersistentLoginFlag] as string))
            {
                FlagFound = true;
                TempData.Remove(PersistentLoginFlag);
            }
    
            // Try to refresh a persistent login the first time an anonymous user hits the index page in this session
            if(!User.Identity.IsAuthenticated && !PersistentLoginAttempted)
            {
                PersistentLoginAttempted = true;
                // If there was a flag, this is the return-trip from a failed persistent login attempt.
                if(!FlagFound)
                {
                    // No flag was found. Create it, then begin the OIDC challenge flow.
                    TempData[PersistentLoginFlag] = PersistentLoginFlag;
                    return Challenge("persistent");
                }
            }
            return Page();
        }
    }
    
        2
  •  -1
  •   Benoit    5 年前

    为什么不让认证cookie永久化呢?这里有另一种方法。。。然后,您可以对照身份验证服务器检查服务器上的身份验证。

    在客户端AddOpenIdConnect上:

                    options.Events = new OpenIdConnectEvents {
                        OnRedirectToIdentityProvider = context => {
                            context.Properties.RedirectUri = context.Request.Path;
                            return Task.FromResult(0);
                        },
                        OnTicketReceived = context =>
                        {
                            context.Properties.IsPersistent = true;
                            context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(15);
                            context.Properties.AllowRefresh = true;
    
                            return Task.FromResult(0);
                        }
                    };