首页 > 解决方案 > 有条件地使用 ADFS 登录

问题描述

我需要为我们的一位客户集成 ADFS。

我们正在使用 OWIN 和 OAuth Bearer 令牌,以及 JWT Bearer Auth。所有这些都运作良好。输入用户名(电子邮件)和密码,针对我们的数据库用户存储进行身份验证,获取不记名令牌。

当输入的用户名位于特定域(客户端的域)中时,我们只需要请求客户端的 ADFS 服务器进行身份验证。(我们不需要询问密码;登录已修改为先询问电子邮件,检查是否是客户域,如果不是,则询问密码 - 看看 Box.com 是如何做到的,这非常相似。 )

我们目前根本不使用 AD,也没有工厂可以这样做,这意味着网络上的所有“联合”示例对我们没有任何帮助,因为它们假设您已经在使用 ADFS 或 Azure AD,而我们不是。

我们的移动应用程序正在使用 ADAL 1.0 库,并且在某种程度上运行良好(它还需要 #2 才能运行)

1) 我不知道如何将用户的浏览器重定向到客户的 ADFS 登录页面。一切都使用一个库,所有这些库都是在编译时配置的——而我不知道将用户发送到哪个客户端的 ADFS,直到该用户在运行时实际启动登录过程。此外,这不使用 Azure AD,这是一个本地服务器 - 所以我不能使用 Microsoft 网关。

2)回调需要解析用户的令牌,确保它是给我们的,通过所有的签名检查,然后我们发出我们自己的不记名令牌——我们不能使用客户端的令牌。这应该相对容易,但我不知道如何告诉 ADFS 端点回调 URL 是什么。

是的,我知道编写这些库的原因是为了让它更难搞砸。我没有太多选择——这些库想要接管整个过程,但我想有条件地调用它们。

更多信息:

客户端 ADFS 为 3.0

我有能力为此使用 OWIN 的身份验证,如果按下,可能会在编译时设置客户端。

标签: asp.netasp.net-mvcauthenticationadfs

解决方案


行。我敢肯定我会为此做些废话,但是就这样吧。

我们最终有条件地使用了 OAuth2 - 检查用户的电子邮件地址,查看域,并为他们的单点登录体验加载配置信息。

我找不到可以有条件地调用的 OWIN 的任何 OAuth2 中间件(即使我使用字幕和被动模式配置 ADAL 库也无法工作),因此我最终使用正确的参数手动重定向到 IDP。我们的移动开发人员将 ADAL 1.0 与 Xamarin 一起使用,因此我能够从该工作流 ( https://adfs.example.net/oauth2/authorize ) 中获取 OAuth2 URL 并在那里重定向。

要解决第 1 步:我创建了一个 OAuth2 回调端点(必须向 ADFS 注册!我有一个查询字符串参数,它提示要加载哪个 SSO 配置,但根本不能改变 - 它必须始终相同那个 IdP),并设置一种方法来交换令牌的“代码”查询字符串参数。这是一个相当简单的 HTTPS 调用,但您必须自己验证令牌(这是我的要求步骤 2 所必需的),并有一堆代码来挖掘电子邮件地址/用户名声明。您必须设置授权类型来传输它,并准备好处理不同的声明名称空间。请记住传递您的回调 URL - OAuth2 标准要求您再次传递它,保持不变 - 否则您的代码无效。

在使用优秀的库验证令牌是有效令牌之后System.IdentityModel.Tokens.Jwt(您需要参考 Stack Overflow 上有关如何以 PEM 格式加载 X509 证书的其他问题,因为我们希望将 IDP 的 pubkey 以 ascii-armored PEM 格式存储在数据库 - 获取它的代码此时被认为是一个不错的选择),并且适用于预期的受众,并且发行者是您预期的发行者(以防有人试图弄乱域提示)并且签名匹配(捕获了大多数早期案例),并且它与用户输入的电子邮件地址匹配(这是 OWIN 'External Cookie' 中间件所做的,当你可以使用它时):

然后,只有这样,您才能发出令牌。

我必须在我们的启动代码中存储对我们的 JWT 保护库的引用(我们正在使用 Autofac):

builder.RegisterInstance(customFormat).As<ISecureDataFormat<AuthenticationTicket>>();

ISecureDataFormat 是为保护/取消保护 JWT 密钥而注册的。我无法找到一种方法来利用 OWIN 身份验证对象来找到一种方法来找到这个实例作为某个地方的属性,所以我不得不在我们的 IoC 模块中存储对它的引用。

我们的登录代码(自定义身份验证提供程序)如下所示:

        var cookiesIdentity = await _userManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
        Request.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, cookiesIdentity);

        // login
        var properties = new AuthenticationProperties(new Dictionary<string, string>
        {
            { "userName", globalUser.UserName },
            { "userId", globalUser.Id.ToString() }
        })
        {
            IsPersistent = true,
            IssuedUtc = DateTime.UtcNow,
            ExpiresUtc = DateTime.UtcNow.AddDays(28),
        };

        // jwt
        var oAuthIdentity = await _userManager.CreateIdentityAsync(user, "JWT");
        oAuthIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()));
        oAuthIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Email, user.Email));
        oAuthIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.GivenName, user.Contact.ContactFirstName + " " + user.Contact.ContactLastName));

        Microsoft.Owin.Security.AuthenticationTicket at = new Microsoft.Owin.Security.AuthenticationTicket(oAuthIdentity, properties);
        string jwt = Startup.Resolve<ISecureDataFormat<AuthenticationTicket>>().Protect(at);

字符串 'jwt' 是实际的原始身份验证票证,我们将其存储在本地存储中以对 API 进行身份验证。(是的,我们的应用很奇怪。您登录到 MVC 页面,但页面使用 JWT 令牌调用 API,就像移动应用一样。它不是 SPA,但它的行为有点像。)

解决步骤 2:Microsoft ADAL 库返回的不是来自 OAuth2 进程的授权代码,而是来自 ADFS 的实际令牌。我建立了另一个端点,该端点在步骤 1 解决方案的中间开始该过程,并调用相同的方法来验证令牌、提取用户名/电子邮件并返回不记名令牌。ADAL 库已经验证登录表单上的用户名输入与令牌中的声明匹配,但服务器需要在将令牌交换为我们的 JWT 不记名令牌之前验证令牌是否合法。

我敢肯定这会容易得多,但老实说,OAuth2 手动操作并不难——但只有在你被迫时才这样做。OWIN 中间件经过充分测试,涵盖了我下个月要寻找的极端情况——它只是不支持跳过很多步骤。


推荐阅读