双重 OWIN 身份验证无法协同工作

Posted

技术标签:

【中文标题】双重 OWIN 身份验证无法协同工作【英文标题】:Double OWIN authentication not working together 【发布时间】:2018-06-08 09:54:33 【问题描述】:

对于我的网站,我正在使用 OWIN OpenId 与第 3 方身份验证提供商进行集成,以允许访问者注册/登录/退出。 “第二”

我的应用程序也有一个测试环境,在将这些更改推送到生产环境之前,所有代码更改都会在该环境中进行测试。我使用 OWIN OpenId 以及“First”,通过另一个 3rd 方身份验证提供程序保护测试环境免受公共访问。只有经过身份验证的访问者才能访问测试环境网站。

现在的问题是它们都独立工作,但我似乎无法将它们结合起来。我想要实现的是,我可以通过使用 First 进行身份验证来访问测试环境,然后作为常规访问者,使用 Second 进行身份验证以查看为注册访问者设计的内容。

这就是我正在做的事情:

两个身份验证提供程序都使用 cookie 身份验证,但我给了它们一个不同的 AuthenticationType 以将它们分开。

if (IsEnabled("First"))
    app.SetDefaultSignInAsAuthenticationType("First");
else
    app.SetDefaultSignInAsAuthenticationType("Second");

// Configure First.
if (IsEnabled("First")) 
    app.UseCookieAuthentication(First.CookieAuthenticationOptions); // AuthenticationType is set to "First" in these options.
    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    
        ClientId = First.AADClientId,
        Authority = First.Authority,
        Notifications = new OpenIdConnectAuthenticationNotifications
        
            AuthenticationFailed = context =>  ... ,
            RedirectToIdentityProvider = context =>  ... 
        ,
        AuthenticationType = "First"
    );
    app.Map($"First.Path/login", config =>
    
        config.Run(context =>
        
            context.Authentication.Challenge(new AuthenticationProperties
                 RedirectUri = First.ReturnUrl, IsPersistent = true  ,
                "First"
            );

            context.Response.StatusCode = 401;
            return context.Response.WriteAsync(string.Empty);
        );
    );


// Configure Second.
app.UseCookieAuthentication(Second.CookieAuthenticationOptions); // AuthenticationType is set to "Second" in these options.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions

    UseTokenLifetime = false,
    Notifications = new OpenIdConnectAuthenticationNotifications
    
        AuthenticationFailed = x => ...,
        RedirectToIdentityProvider = x =>
        
            var mgr = x.Options.ConfigurationManager as PolicyConfigurationManager;
            if (x.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
            
                var config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None,
                x.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary["PolicyId"]);
                x.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint;
            
            else
            
                var config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None,
                x.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary["PolicyId"]);
                x.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint;
            

            var redirectUri = Second.ReturnPath;
            x.ProtocolMessage.RedirectUri = redirectUri;
            x.ProtocolMessage.PostLogoutRedirectUri = redirectUri;
        ,
        SecurityTokenValidated = x => ...
    ,
    Scope = "openid",
    ResponseType = "id_token",
    ReturnUri = Second.ReturnUri,
    ClientId = Second.ClientId,
    ConfigurationManager = GetConfigurationManager()
    AuthenticationType = configuration.AuthenticationType
);
app.Map(Second.LoginPath, config =>

    // Trigger unauthorized so that active authentication will redirect to active directory.
    config.Run(context =>
    
        // Set policy in context to mitigate null ref exception in Startup.Auth OnRedirectToIdentityProvider
        context.Authentication.Challenge(
            new AuthenticationProperties(new Dictionary<string, string>
            
                "PolicyId", Second.LoginPolicyId
            )
            
                IsPersistent = true,
                RedirectUri = returnUrl
            , "Second");

        context.Response.StatusCode = 401;

        // Middleware will redirect us instead of using this output.
        return context.Response.WriteAsync(string.Empty);
    );
);
app.Map(Second.ReturnPath, config =>

    config.Use((context, next) =>
    
        // In case of login, we will never get here because we will get redirected by middleware.
        context.Response.Redirect("/");

        return Task.FromResult(0);
    );
);

启用 First 后,我​​可以这样做

var identity = HttpContext.Current.GetOwinContext.Authentication.AuthenticateAsync("Second").Result?.Identity;

在后续请求中并具有 ClaimsIdentity。但是当 First 被启用时,由于某种原因,上面的 Result 为空。

我注意到,当我同时启用 First 和 Second,并将 DefaultSignInAsAuthenticationType 设置为“Second”时,First 不再起作用。如果我同时启用 First 和 Second,并使用以前的 First 身份验证 cookie 浏览网站,一切正常。

我猜想在某处设置身份验证 cookie 的返回方法需要一些 AuthenticationType 的引用,但我不知道在哪里做。

我错过了什么?

【问题讨论】:

【参考方案1】:

诀窍是在配置 Second 时将 AuthenticationType 添加到 TokenValidationParameters 中,如下所示:

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions

    UseTokenLifetime = false,
    Notifications = new OpenIdConnectAuthenticationNotifications
    
        AuthenticationFailed = x => ...,
        RedirectToIdentityProvider = x =>
        
            var mgr = x.Options.ConfigurationManager as PolicyConfigurationManager;
            if (x.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
            
                var config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None,
                x.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary["PolicyId"]);
                x.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint;
            
            else
            
                var config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None,
                x.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary["PolicyId"]);
                x.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint;
            

            var redirectUri = Second.ReturnPath;
            x.ProtocolMessage.RedirectUri = redirectUri;
            x.ProtocolMessage.PostLogoutRedirectUri = redirectUri;
        ,
        SecurityTokenValidated = x => ...
    ,
    Scope = "openid",
    ResponseType = "id_token",
    ReturnUri = Second.ReturnUri,
    ClientId = Second.ClientId,
    ConfigurationManager = GetConfigurationManager(),
    AuthenticationType = configuration.AuthenticationType,
    // ADD THIS TO MAKE IT WORK:
    TokenValidationParameters = new TokenValidationParameters
        
            AuthenticationType = configuration.AuthenticationType
        
);

【讨论】:

以上是关于双重 OWIN 身份验证无法协同工作的主要内容,如果未能解决你的问题,请参考以下文章

IsPersistent 在 OWIN Cookie 身份验证中的工作原理

核心 5,PasswordSignInAsync 无法在双重身份验证方案中设置 cookie

实施 Identity 2.1 + OWIN OAuth JWT 不记名令牌时如何从 Web API 控制器端点进行身份验证

OWIN Cookie 身份验证 - 使用 Kerberos 委派模拟 SQL Server

使用 Asp.Net 下的 owin 和 LinkedIn 身份验证提供程序进行 WebApi 身份验证

Google 帐户登录错误(双重身份验证)[关闭]