如何通过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)。
您可以基于与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。