如何通过C#控制台应用程序对使用Forms Authentication的ASP.NET WebAPI进行身份验证

本文关键字:NET ASP WebAPI 身份验证 Authentication Forms 控制台 何通过 应用程序 | 更新日期: 2023-09-27 17:58:19

我有一个可用的ASP.NET MVC 4 web应用程序。我已经编写了自己的RoleProvider,并且使用标准的[Authorize]属性。我的控制器是这样的:

[Authorize(Roles="ContactAdmins")] //System.Web.Mvc
public ActionResult Index()

我想在我的应用程序中添加一个WebAPI控制器,并利用我现有的管道

[Authorize(Roles="ContactAdmins")] //System.Web.Http
public IEnumerable<Contact> Get()

这适用于我的站点中的Javascript ajax调用(因为浏览器用户已经通过Forms auth cookie进行了身份验证)。我的问题是,从C#控制台应用程序(或任何其他不属于我的web应用程序的应用程序),我如何对这个API进行身份验证?

让我们假设,对于我的API的公共部分,我使用的代码与在MVC3中消费WebApi的问题中发现的代码非常相似。

var url = "http://localhost:9000/api/contacts";
using (var client = new WebClient())
using (var reader = XmlReader.Create(client.OpenRead(url)))
{
    var serializer = new XmlSerializer(typeof(Contact[]));
    var contacts = (Contact[])serializer.Deserialize(reader);
    // TODO: Do something with the contacts
}

我需要在这里修改什么?还是我不得不放弃这个,用一种完全不同的方法?我不想使用Forms进行远程客户端的API身份验证,但我想保留当前用于作为应用程序一部分的JavaScript客户端的优雅方法(由于设置了表单cookie,只需请求API)。

如何通过C#控制台应用程序对使用Forms Authentication的ASP.NET WebAPI进行身份验证

您可以基于与Forms Auth相同的原语,将标准Forms Auth与自定义Basic Auth相结合。注意,对于Basic,强烈建议使用HTTPS(事实上,现在越来越多的Windows组件默认不支持Basic+HTTP)。

下面是一个基本身份验证模块的示例代码,该模块重用Forms Auth中的代码。它还附带了自己的配置部分(名为"basicAuth")。您需要确保两个身份验证(Forms和Basic)在一起配置时使用相同的cookie和参数:

using System;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
namespace MySecurity
{
    public class BasicAuthenticationModule : IHttpModule
    {
        public event EventHandler<BasicAuthenticationEventArgs> Authenticate;
        public void Dispose()
        {
        }
        protected virtual string GetRealm(HttpContext context)
        {
            return BasicAuthenticationSection.Current.GetRealm(context);
        }
        public virtual void Init(HttpApplication context)
        {
            context.AuthenticateRequest += OnAuthenticateRequest;
            context.EndRequest += OnEndRequest;
        }
        protected virtual bool FormsAuthenticate(HttpContext context, string login, string password, string realm)
        {
            // check ad-hoc forms credentials, as we can support it even if forms auth is not configured
            FormsAuthenticationConfiguration c = ((AuthenticationSection)ConfigurationManager.GetSection("system.web/authentication")).Forms;
            if ((c.Credentials == null) || (c.Credentials.Users == null))
                return false;
            foreach (FormsAuthenticationUser user in c.Credentials.Users)
            {
                if ((string.Compare(user.Name, login, true, CultureInfo.CurrentCulture) == 0) &&
                    (string.Compare(user.Password, password, true, CultureInfo.CurrentCulture) == 0))
                    return true;
            }
            return false;
        }
        protected virtual bool OnAuthenticate(HttpContext context, string login, string password, string realm)
        {
            EventHandler<BasicAuthenticationEventArgs> handler = Authenticate;
            if (handler != null)
            {
                BasicAuthenticationEventArgs e = new BasicAuthenticationEventArgs(context, login, password, realm);
                handler(this, e);
                return !e.Cancel;
            }
            return FormsAuthenticate(context, login, password, realm);
        }
        protected virtual string[] GetUserRoles(HttpContext context, string login, string realm)
        {
            // TODO: overwrite if needed
            return new string[0];
        }
        protected virtual IPrincipal GetUser(HttpContext context, FormsAuthenticationTicket ticket)
        {
            return new GenericPrincipal(new BasicAuthenticationIdentity(ticket), GetUserRoles(context, ticket.Name, GetRealm(context)));
        }
        protected virtual void OnAuthenticated(HttpContext context)
        {
        }
        protected virtual void OnEndRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            if (application.Response.StatusCode != (int)HttpStatusCode.Unauthorized)
                return;
            string basic = "Basic Realm='"" + GetRealm(application.Context) + "'"";
            application.Response.AppendHeader("WWW-Authenticate", basic);
        }
        public static void SignOut()
        {
            if (HttpContext.Current == null)
                return;
            HttpContext.Current.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
            HttpContext.Current.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
            HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
            cookie.Expires = DateTime.Now.AddDays(-1);
            HttpContext.Current.Response.Cookies.Add(cookie);
        }
        public static bool IsAuthenticated(HttpContext context)
        {
            if ((context == null) || (context.User == null) || (context.User.Identity == null))
                return false;
            return context.User.Identity.IsAuthenticated;
        }
        protected virtual void OnAuthenticateRequest(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            if ((IsAuthenticated(application.Context)) && (!BasicAuthenticationSection.Current.ReAuthenticate))
                return;
            string encryptedTicket;
            FormsAuthenticationTicket ticket;
            HttpCookie cookie = application.Context.Request.Cookies[BasicAuthenticationSection.Current.Name];
            if (cookie == null)
            {
                // no cookie, check auth header
                string authHeader = application.Context.Request.Headers["Authorization"];
                if ((string.IsNullOrEmpty(authHeader)) || (!authHeader.StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase)))
                {
                    ResponseAccessDenied(application);
                    return;
                }
                string login;
                string password;
                string lp = authHeader.Substring(6).Trim();
                if (string.IsNullOrEmpty(lp))
                {
                    ResponseAccessDenied(application);
                    return;
                }
                lp = Encoding.Default.GetString(Convert.FromBase64String(lp));
                if (string.IsNullOrEmpty(lp.Trim()))
                {
                    ResponseAccessDenied(application);
                    return;
                }
                int pos = lp.IndexOf(':');
                if (pos < 0)
                {
                    login = lp;
                    password = string.Empty;
                }
                else
                {
                    login = lp.Substring(0, pos).Trim();
                    password = lp.Substring(pos + 1).Trim();
                }
                if (!OnAuthenticate(application.Context, login, password, GetRealm(application.Context)))
                {
                    ResponseAccessDenied(application);
                    return;
                }
                // send cookie back to client
                ticket = new FormsAuthenticationTicket(login, false, (int)BasicAuthenticationSection.Current.Timeout.TotalMinutes);
                encryptedTicket = FormsAuthentication.Encrypt(ticket);
                cookie = new HttpCookie(BasicAuthenticationSection.Current.Name, encryptedTicket);
                application.Context.Response.Cookies.Add(cookie);
                // don't overwrite context user if it's been set
                if ((!IsAuthenticated(application.Context)) || (BasicAuthenticationSection.Current.ReAuthenticate))
                {
                    application.Context.User = GetUser(application.Context, ticket);
                }
                OnAuthenticated(application.Context);
                application.Context.Response.StatusCode = (int)HttpStatusCode.OK;
                return;
            }
            // there is a cookie, check it
            encryptedTicket = cookie.Value;
            if (string.IsNullOrEmpty(encryptedTicket))
            {
                ResponseAccessDenied(application);
                return;
            }
            try
            {
                ticket = FormsAuthentication.Decrypt(encryptedTicket);
            }
            catch
            {
                ResponseAccessDenied(application);
                return;
            }
            if (ticket.Expired)
            {
                ResponseAccessDenied(application);
                return;
            }
            // set context user
            // don't overwrite context user if it's been set
            if ((!IsAuthenticated(application.Context) || (BasicAuthenticationSection.Current.ReAuthenticate)))
            {
                application.Context.User = GetUser(application.Context, ticket);
            }
            OnAuthenticated(application.Context);
        }
        protected virtual void WriteAccessDenied(HttpApplication application)
        {
            if (application == null)
                throw new ArgumentNullException("application");
            application.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            application.Context.Response.StatusDescription = "Unauthorized";
            application.Context.Response.Write(application.Context.Response.StatusCode + " " + application.Context.Response.StatusDescription);
        }
        protected virtual void ResponseAccessDenied(HttpApplication application)
        {
            // if there is a bad cookie, kill it
            application.Context.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
            application.Context.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
            HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
            cookie.Expires = DateTime.Now.AddDays(-1);
            HttpContext.Current.Response.Cookies.Add(cookie);
            WriteAccessDenied(application);
            application.CompleteRequest();
        }
    }
    public class BasicAuthenticationSection : ConfigurationSection
    {
        public const string SectionName = "basicAuth";
        private const string DefaultCookieName = "." + SectionName;
        private static BasicAuthenticationSection _current;
        public static BasicAuthenticationSection Current
        {
            get
            {
                return _current ?? (_current = ConfigurationManager.GetSection(SectionName) as BasicAuthenticationSection ?? new BasicAuthenticationSection());
            }
        }
        [StringValidator(MinLength = 1), ConfigurationProperty("name", DefaultValue = DefaultCookieName)]
        public string Name
        {
            get
            {
                return (string)base["name"];
            }
        }
        internal string GetRealm(HttpContext context)
        {
            if (!string.IsNullOrEmpty(Realm))
                return Realm;
            return context.Request.Url.Host;
        }
        [ConfigurationProperty("realm", DefaultValue = "")]
        public string Realm
        {
            get
            {
                return (string)base["realm"];
            }
        }
        [ConfigurationProperty("domain", DefaultValue = "")]
        public string Domain
        {
            get
            {
                return (string)base["domain"];
            }
        }
        [ConfigurationProperty("reAuthenticate", DefaultValue = false)]
        public bool ReAuthenticate
        {
            get
            {
                return (bool)base["reAuthenticate"];
            }
        }
        [TypeConverter(typeof(TimeSpanMinutesConverter)), ConfigurationProperty("timeout", DefaultValue = "30"), PositiveTimeSpanValidator]
        public TimeSpan Timeout
        {
            get
            {
                return (TimeSpan)base["timeout"];
            }
        }
    }
    public class BasicAuthenticationIdentity : IIdentity
    {
        public BasicAuthenticationIdentity(FormsAuthenticationTicket ticket)
        {
            if (ticket == null)
                throw new ArgumentNullException("ticket");
            Ticket = ticket;
        }
        public FormsAuthenticationTicket Ticket;
        public string AuthenticationType
        {
            get
            {
                return BasicAuthenticationSection.SectionName;
            }
        }
        public bool IsAuthenticated
        {
            get
            {
                return true;
            }
        }
        public string Name
        {
            get
            {
                return Ticket.Name;
            }
        }
    }
    public class BasicAuthenticationEventArgs : CancelEventArgs
    {
        public BasicAuthenticationEventArgs(HttpContext context, string login, string password, string realm)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            Context = context;
            Login = login;
            Password = password;
            Realm = realm;
        }
        public HttpContext Context { get; private set; }
        public string Realm { get; private set; }
        public string Login { get; private set; }
        public string Password { get; private set; }
        public IPrincipal User { get; set; }
    }
}

一旦在服务器端安装了它,就可以将WebClient配置为使用基本身份验证:

WebClient client = new WebClient();
client.Credentials =  new NetworkCredential("username", "password");

有许多方法可以与控制台应用程序共享cookie。看看这里的一些想法:

http://netpl.blogspot.com/2008/02/clickonce-webservice-and-shared-forms.html

另一个简单的选择是公开一个不需要任何身份验证的web方法,获取用户名和密码并将cookie返回给客户端。

无论采取何种方法,您的目标都是在控制台应用程序端以某种方式获取表单cookie。从那里你可以很容易地完成,因为你所做的就是将cookie附加到你的请求中。web api将愉快地接受cookie。