使用 ADFS 对 ASP.NET Web Api 进行身份验证

Posted

技术标签:

【中文标题】使用 ADFS 对 ASP.NET Web Api 进行身份验证【英文标题】:Authentication to ASP.NET Web Api using ADFS 【发布时间】:2017-07-04 05:45:34 【问题描述】:

我需要访问使用 ADFS 进行身份验证的 ASP.NET Web Api。通过 ADFS 登录门户并获取相关的 FedAuth cookie,我可以通过浏览器可靠地访问它。不幸的是,我需要从专用浏览器外部访问它才能在移动应用程序中使用。该项目几乎是为工作和学校身份验证(本地)设置并设置为 cookie 身份验证的标准 Visual Studio Web API 模板的略微修改版本。

来自 Startup.Auth.cs 的一些代码:

public void Configuration(IAppBuilder app)

    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    app.UseWsFederationAuthentication(
        new WsFederationAuthenticationOptions
        
            Wtrealm = realm,
            MetadataAddress = adfsMetadata
        );
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    
        AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
    );

我似乎不知道从哪里开始。我尝试从 ADFS 请求访问令牌,并且可以使用相关登录信息获取不同版本的 SAML 断言,但它被 Web API 拒绝。我是否误解了它应该如何工作?

据我了解,它应该是这样的: How I think it's supposed to work

    应用从 ADFS 请求身份验证令牌 如果提供的信息正确,ADFS 会给请求者一个身份验证令牌 应用程序向 Web API 发出请求,并将令牌作为 base64 编码字符串发送到名为 FedAuth(默认情况下)的 cookie 中 Web Api 将令牌发送到 ADFS 以查明令牌是否正确。 ADFS 响应某种成功消息 Web Api 以拒绝或数据片段响应应用程序,具体取决于身份验证的方式。

这就是我现在试图弄清楚如何获得正确令牌时所拥有的。

using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.Net;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Security;
using Thinktecture.IdentityModel.Extensions;
using Thinktecture.IdentityModel.WSTrust;

namespace ConsoleApplication1
    
    class Program
    
        private const string UserName     = "USERNAME";
        private const string Password     = "PASSWORD";
        private const string Domain       = "DOMAIN";
        private const string ADFSEndpoint = "ADFS ENDPOINT";
        private const string ApiBaseUri   = "THE API";
        private const string ApiEndPoint  = "AN ENDPOINT";

        static void Main(string[] args)
        
            SecurityToken token = RequestSecurityToken(); // Obtain security token from ADFS.
            CallApi(token);                               // Call api. 
            Console.ReadKey();                            // Stop console from closing
        

        private static SecurityToken RequestSecurityToken()
        
            var trustChannelFactory =
                new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
                    new EndpointAddress(new Uri(ADFSEndpoint)))
                
                    TrustVersion = TrustVersion.WSTrust13,
                    Credentials =  UserName =  UserName = UserName + "@" + Domain, Password = Password  ,
                ;

            var requestSecurityToken = new RequestSecurityToken
            
                RequestType = RequestTypes.Issue,
                KeyType = KeyTypes.Bearer,
                AppliesTo = new EndpointReference(ApiBaseUri)
            ;

            RequestSecurityTokenResponse response;
            var securityToken = trustChannelFactory.CreateChannel().Issue(requestSecurityToken, out response);

            return securityToken;
        

        private static async void CallApi(SecurityToken securityToken)
        
            using (var handler = new HttpClientHandler  CookieContainer = new CookieContainer() )
            
                using (var client = new HttpClient(handler))
                
                    handler.CookieContainer.MaxCookieSize = 8000; // Trying to make sure I can fit it in the cookie

                    var cookie = new Cookie 
                        Name = "FedAuth",
                        Value = Base64Encode(securityToken.ToTokenXmlString()),
                        HttpOnly = true,
                        Secure = true
                    ;
                    handler.CookieContainer.Add(new Uri(ApiBaseUri), cookie);
                    var response = client.GetAsync(new Uri(ApiBaseUri + ApiEndPoint)).Result;
                    string result = await response.Content.ReadAsStringAsync();
                    Console.WriteLine(result);
                
            
        

        public static string Base64Encode(string plainText)
        
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
            return System.Convert.ToBase64String(plainTextBytes);
        
    

我不太记得我的示例基于什么代码,但如果有人能指出我正确的方向或告诉我我在哪里搞砸了,我将不胜感激。

编辑:抱歉,忘记添加我得到的内容。 Web Api 吐出一堆调试信息,因为抛出了异常,告诉我需要一个 SecurityContextToken 而不是我显然得到的 saml:Assertion。也许我的 googlefoo 不够强大,但我似乎不知道从哪里开始。我可以设置 api 以接受 SAML 断言还是需要以其他方式请求令牌?

【问题讨论】:

【参考方案1】:

您不能使用 WS-Fed 调用 Web API。您需要像Calling a web API in a web app using Azure AD and OpenID Connect 一样的 OpenID Connect / OAuth。

它适用于 Azure AD,但它确实说明了流程。

什么版本的 ADFS?

如果是 2.0,则不支持 OAuth。 如果是 3.0,仅限 Web API - 请参阅 Securing a Web API with ADFS on WS2012 R2 Got Even Easier。 如果是 4.0,您拥有完整的堆栈。

【讨论】:

看起来 ADFS 服务器是 2.0,但很快就会升级。到目前为止,我已经应用了一些拼凑解决方案(OAuth 令牌生成和 Web api 验证),但只是为了让其他人找到这篇文章,你会建议什么作为这个问题的可接受解决方案?跨度> 使用身份服务器并与 ADFS 联合。 我也在尝试调用 web api,但它不是使用 wpf 应用程序调用的。调用时授权失败。如果我从控制器中删除了授权属性,它将被调用

以上是关于使用 ADFS 对 ASP.NET Web Api 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP.NET Core Web API 中添加两个不同的令牌

在 ASP.Net 应用程序上结合 ADFS 身份验证和 JWT 承载

为Azure Web Site 添加ADFS验证支持之二 在代码里使用ADFS

ADFS 2.0 Web 应用注销

使用 ADFS 的 JWT Bearer 身份验证将 ASP.NET Framework 迁移到 ASP.NET Core 3.1

针对 ADAM 和 AD FS 的 ASP.NET MVC 3 SSO