HttpClient calling a Windows-Authenication ApiController Method…but no WindowsIdentity coming along for the ride

后端 未结 1 1655
傲寒
傲寒 2021-01-22 16:20

Is there a way for my api controller to get the IIdentity of the account who initiated the call to the api controller when the api-controller is using windows-authentication ?

相关标签:
1条回答
  • 2021-01-22 16:38

    Ok. I figured out the issue. Thanks to this post.

    How to get Windows user name when identity impersonate="true" in asp.net?

    //Start Quote//

    With <authentication mode="Windows"/> in your application and Anonymous access enabled in IIS, you will see the following results:

    System.Environment.UserName: Computer Name
    Page.User.Identity.Name: Blank
    System.Security.Principal.WindowsIdentity.GetCurrent().Name: Computer Name
    

    //End Quote

    So I'll also include a full answer.......to show the issue and some possible settings that need to be tweaked.

    Go and download this mini example.

    https://code.msdn.microsoft.com/ASP-NET-Web-API-Tutorial-8d2588b1

    This will give you a quick "WebApiTier" called ProductsApp (ProductsApp.csproj).

    If you want to do it yourself....just create a WebApi Controller...that returns some Products.

    public class ProductsController : ApiController
    {
        Product[] products = new Product[] 
        { 
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, 
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, 
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } 
        };
    
        [IdentityWhiteListAuthorization]
        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }
    
    }
    

    Open the above .sln.

    Add a new "class library" csproj called "WebApiIdentityPoc.Domain.csproj".

    Create a new class in this library.

    namespace WebApiIdentityPoc.Domain
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Category { get; set; }
            public decimal Price { get; set; }
        }
    }
    

    Delete (or comment out)

    \ProductsApp\Models\Product.cs

    Add a (project) reference in ProductsApp to WebApiIdentityPoc.Domain.

    Fix the namespace issue in

    \ProductsApp\Controllers\ProductsController.cs

    //using ProductsApp.Models;
    using WebApiIdentityPoc.Domain;
    
    namespace ProductsApp.Controllers
    {
        public class ProductsController : ApiController
        {
    

    (You're basically moving the "Product" object to another library so the Server and the Client can share the same object.)

    You should be able to compile at this point.

    ..........

    Add a new "Console Application" projec to the solution.

    WebApiIdentityPoc.ConsoleOne.csproj

    Use Nuget to add "Newtonsoft.Json" reference/library to the WebApiIdentityPoc.ConsoleOne.csproj.

    Add the references (Framework or Extensions using right-click/add references on the "/References folder in the csproj)

    System.Net.Http
    System.Net.Http.Formatting
    System.Net.Http.WebRequest (this one is may not be needed)
    

    Add a project reference to WebApiIdentityPoc.Domain.

    In "Program.cs" in the Console App, paste this code: .............

    namespace WebApiIdentityPoc.ConsoleOne
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Net;
        using System.Net.Http;
        using System.Net.Http.Headers;
        using System.Security.Principal;
        using System.Text;
        using System.Threading.Tasks;
        using Newtonsoft.Json;
        using WebApiIdentityPoc.Domain;
    
        public class Program
        {
            private static readonly string WebApiExampleUrl = "http://localhost:47503/api/Products/GetAllProducts"; /* check ProductsApp.csproj properties, "Web" tab, "IIS Express" settings if there is an issue */
    
            public static void Main(string[] args)
            {
                try
                {
                    System.Security.Principal.WindowsIdentity ident = System.Security.Principal.WindowsIdentity.GetCurrent();
                    if (null != ident)
                    {
                        Console.WriteLine("Will the Identity '{0}' Show up in IdentityWhiteListAuthorizationAttribute ???", ident.Name);
                    }
    
                    RunHttpClientExample();
                    RunWebClientExample();
                    RunWebClientWicExample();
                }
                catch (Exception ex)
                {
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    Exception exc = ex;
                    while (null != exc)
                    {
                        sb.Append(exc.GetType().Name + System.Environment.NewLine);
                        sb.Append(exc.Message + System.Environment.NewLine);
                        exc = exc.InnerException;
                    }
    
                    Console.WriteLine(sb.ToString());
                }
    
                Console.WriteLine("Press ENTER to exit");
                Console.ReadLine();
            }
    
            private static void RunWebClientExample()
            {
                /* some articles said that HttpClient could not pass over the credentials because of async operations, these were some "experiments" using the older WebClient.  Stick with HttpClient if you can */
                WebClient webClient = new WebClient();
                webClient.UseDefaultCredentials = true;
                string serviceUrl = WebApiExampleUrl;
                string json = webClient.DownloadString(serviceUrl);
                IEnumerable<Product> returnItems = JsonConvert.DeserializeObject<IEnumerable<Product>>(json);
                ShowProducts(returnItems);
            }
    
            private static void RunWebClientWicExample()
            {
                /* some articles said that HttpClient could not pass over the credentials because of async operations, these were some "experiments" using the older WebClient.  Stick with HttpClient if you can */
                System.Security.Principal.WindowsIdentity ident = System.Security.Principal.WindowsIdentity.GetCurrent();
                WindowsImpersonationContext wic = ident.Impersonate();
                try
                {
                    WebClient webClient = new WebClient();
                    webClient.UseDefaultCredentials = true;
                    string serviceUrl = WebApiExampleUrl;
                    string json = webClient.DownloadString(serviceUrl);
                    IEnumerable<Product> returnItems = JsonConvert.DeserializeObject<IEnumerable<Product>>(json);
                    ShowProducts(returnItems);
                }
                finally
                {
                    wic.Undo();
                }
            }
    
            private static void RunHttpClientExample()
            {
                IEnumerable<Product> returnItems = null;
    
                HttpClientHandler handler = new HttpClientHandler()
                {
                    UseDefaultCredentials = true, PreAuthenticate = true
                };
    
                ////////WebRequestHandler webRequestHandler = new WebRequestHandler();
                ////////webRequestHandler.UseDefaultCredentials = true;
                ////////webRequestHandler.AllowPipelining = true;
                ////////webRequestHandler.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequired;
                ////////webRequestHandler.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Identification;
    
                using (HttpClient client = new HttpClient(handler))
                {
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                    string serviceUrl = WebApiExampleUrl;
    
                    HttpResponseMessage response = client.GetAsync(new Uri(serviceUrl)).Result;
    
                    var temp1 = response.ToString();
                    var temp2 = response.Content.ReadAsStringAsync().Result;
    
                    if (response.IsSuccessStatusCode)
                    {
                        Task<IEnumerable<Product>> wrap = response.Content.ReadAsAsync<IEnumerable<Product>>();
                        if (null != wrap)
                        {
                            returnItems = wrap.Result;
                        }
                        else
                        {
                            throw new ArgumentNullException("Task<IEnumerable<Product>>.Result was null.  This was not expected.");
                        }
                    }
                    else
                    {
                        throw new HttpRequestException(response.ReasonPhrase + " " + response.RequestMessage);
                    }
                }
    
                ShowProducts(returnItems);
            }
    
            private static void ShowProducts(IEnumerable<Product> prods)
            {
                if (null != prods)
                {
                    foreach (Product p in prods)
                    {
                        Console.WriteLine("{0}, {1}, {2}, {3}", p.Id, p.Name, p.Price, p.Category);
                    }
    
                    Console.WriteLine(string.Empty);
                }
            }
        }
    }
    

    You should be able to compile and run and see some Products display in the Console App.

    .....

    In "ProductsApp.csproj", Add a new Folder.

    /WebApiExtensions/

    Under this folder, add a new file:

    IdentityWhiteListAuthorizationAttribute.cs

    Paste in this code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Http;
    using System.Web.Http.Controllers;
    
    namespace ProductsApp.WebApiExtensions
    {
        public class IdentityWhiteListAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
        {
            public IdentityWhiteListAuthorizationAttribute()
            {
            }
    
            private string CurrentActionName { get; set; }
    
            public override void OnAuthorization(HttpActionContext actionContext)
            {
                this.CurrentActionName = actionContext.ActionDescriptor.ActionName;
                base.OnAuthorization(actionContext);
            }
    
            protected override bool IsAuthorized(HttpActionContext actionContext)
            {
    
                var test1 = System.Threading.Thread.CurrentPrincipal;
                var test2 = System.Security.Principal.WindowsIdentity.GetCurrent();
    
                ////string userName = actionContext.RequestContext.Principal.Name;/*  Web API v2  */
                string dingDingDingUserName = string.Empty;
                ApiController castController = actionContext.ControllerContext.Controller as ApiController;
                if (null != castController)
                {
                    dingDingDingUserName = castController.User.Identity.Name;
                }
    
            string status = string.Empty;
            if (string.IsNullOrEmpty(dingDingDingUserName))
            {
                status = "Not Good.  No dingDingDingUserName";
            }
            else
            {
                status = "Finally!";
            }
    
                return true;
            }
        }
    }
    

    Decorate the webapimethod with this attribute.

        [IdentityWhiteListAuthorization]
        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }
    

    (You'll have to resolve the namespace).

    At this point, you should be able to compile....and run.

    But dingDingDingUserName will be string.Empty. (The original issue that spanned this post).

    Ok..

    Click (left-click once) the ProductsApp.csproj in the Solution Explorer.

    Look at the properties tab. (This is not the "right-click / properties ::: This is the properties that show up (default would be in the bottom right of VS) when you simply left-click the ProductsApp.csproj.

    You'll see several settings, but there are two of interest:

    Anonymous Authentication | Enabled
    Windows Authentication | Enabled
    

    (Note, the above is how these settings show up in the VS GUI. They show up like this in the .csproj file)

               <IISExpressAnonymousAuthentication>enabled</IISExpressAnonymousAuthentication>
    <IISExpressWindowsAuthentication>enabled</IISExpressWindowsAuthentication>
    

    If you set

    Anonymous Authentication | Disabled
    

    (which shows up in the .csproj like this:

    <IISExpressAnonymousAuthentication>disabled</IISExpressAnonymousAuthentication>
    <IISExpressWindowsAuthentication>enabled</IISExpressWindowsAuthentication>
    

    )

    VOILA! The "dingDingDingName" value should show up.

    The link I have above .. points to the anonymous-authenication-enabled to being the issue.

    But here is a long example to show the direct effects...in regards to HttpClient.

    One more caveat I learned along the way.

    If you cannot alter the

    Anonymous Authentication Enabled/Disabled
    Windows Authentication Enabled/Disabled
    

    settings, then you need to adjust the "master settings".

    In IIS Express, this will be in a file like:

    C:\Users\MyUserName\Documents\IISExpress\config\applicationhost.config

    The “master settings” need to allow the local settings to be overridden.

    <sectionGroup name="security">
        <section name="anonymousAuthentication" overrideModeDefault="Allow" />
        <!-- Other Stuff -->
        <section name="windowsAuthentication" overrideModeDefault="Allow" />
        </sectionGroup>
    

    The authentications themselves need to be turned on at a master level.

    <security>
        <authentication>
    
        <anonymousAuthentication enabled="true" userName="" />
    
        <windowsAuthentication enabled="true">
                        <providers>
                                        <add value="Negotiate" />
                                        <add value="NTLM" />
                        </providers>
        </windowsAuthentication>
    
        </authentication>
    

    (Full IIS will have similar settings in

    C:\Windows\System32\inetsrv\config\applicationHost.config

    )

    Bottom line:

    HttpClient can send over the WindowsIdentity of the process running the HttpClient code....using HttpClientHandler AND if the WebApiTier is set for WindowsAuthentication AND Anonymous-Authentication turned off.

    Ok. I hope that helps somebody in the future.

    0 讨论(0)
提交回复
热议问题