ASP.NETフォーム認証を使用するアプリケーションがあります。ほとんどの場合、うまく機能していますが、.ashxファイルを介して単純なAPIのサポートを追加しようとしています。 ashxファイルにオプションの認証が必要です(つまり、Authenticationヘッダーを指定しない場合、匿名で機能します)。しかし、何をするかに応じて、特定の条件下で認証を要求します。
必要な認証が提供されていない場合、ステータスコード401で応答するのは簡単なことだと思いましたが、フォーム認証モジュールがそれをインターセプトし、代わりにログインページへのリダイレクトで応答しているようです。つまり、私のProcessRequest
メソッドは次のようになります。
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
}
その後、クライアントで401エラーコードを取得する代わりに、私が予想するように、私は実際ログインページへの302リダイレクトを取得しています。
通常のHTTPトラフィックについては、それがどのように役立つかがわかりますが、APIページについては、401を変更せずに通過させ、クライアント側の呼び出し元がプログラムで応答できるようにします。
それを行う方法はありますか?
ASP.NET 4.5では、ブール値 HttpResponse.SuppressFormsAuthenticationRedirect
プロパティ。
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.SuppressFormsAuthenticationRedirect = true;
}
少し調べてみると、 FormsAuthenticationModule が_HttpApplicationContext.EndRequest
_イベントのハンドラーを追加しているように見えます。ハンドラーでは、401ステータスコードをチェックし、基本的にResponse.Redirect(loginUrl)
を代わりに実行します。私が知る限り、FormsAuthenticationModule
を使用したい場合、この動作をオーバーライドする方法はありません。
私がそれを回避する方法は、web.configのFormsAuthenticationModule
を次のように無効にすることでした。
_<authentication mode="None" />
_
そして、_Application_AuthenticateEvent
_を自分で実装します:
_void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Context.User == null)
{
var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);
if (oldTicket != null && !oldTicket.Expired)
{
var ticket = oldTicket;
if (FormsAuthentication.SlidingExpiration)
{
ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
if (ticket == null)
return;
}
Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
if (ticket != oldTicket)
{
// update the cookie since we've refreshed the ticket
string cookieValue = FormsAuthentication.Encrypt(ticket);
var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??
new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };
if (ticket.IsPersistent)
cookie.Expires = ticket.Expiration;
cookie.Value = cookieValue;
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.HttpOnly = true;
if (FormsAuthentication.CookieDomain != null)
cookie.Domain = FormsAuthentication.CookieDomain;
Context.Response.Cookies.Remove(cookie.Name);
Context.Response.Cookies.Add(cookie);
}
}
}
}
private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
FormsAuthenticationTicket ticket = null;
string encryptedTicket = null;
var cookie = context.Request.Cookies[name];
if (cookie != null)
{
encryptedTicket = cookie.Value;
}
if (!string.IsNullOrEmpty(encryptedTicket))
{
try
{
ticket = FormsAuthentication.Decrypt(encryptedTicket);
}
catch
{
context.Request.Cookies.Remove(name);
}
if (ticket != null && !ticket.Expired)
{
return ticket;
}
// if the ticket is expired then remove it
context.Request.Cookies.Remove(name);
return null;
}
}
_
実際にはそれよりもやや複雑ですが、基本的にはリフレクターのFormsAuthenticationModule
の実装を見ることでコードを得ました。私の実装は、組み込みのFormsAuthenticationModule
とは異なり、401で応答しても何もしません-ログインページにリダイレクトしません。それが要件になったら、自動リダイレクトなどを無効にするためにコンテキストにアイテムを配置できると思います。
これがすべての人に有効かどうかはわかりませんが、IIS7では、ステータスコードと説明を設定した後にResponse.End()を呼び出すことができます。このように、その#&$ ^#@ *! FormsAuthenticationModuleはリダイレクトを行いません。
public void ProcessRequest(HttpContext context) {
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.End();
}
Zacharydlの答えをわずかに構築するために、これを使用して問題を解決しました。すべてのリクエストで、最初に、それがAJAXである場合、すぐに動作を抑制します。
protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
Response.End()がどのように機能したのかわかりません。私は喜んでそれを試してみた後、MSDNでResponse.End()を調べました:「ページの実行を停止し、EndRequestイベントを発生させます」。
ハックする価値があるのは:
_response.StatusCode = 401;
_context.Items["401Override"] = true;
_response.End();
次に、Global.csにEndRequestハンドラーを追加します(認証HTTPModuleの後に呼び出されます)。
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Items["401Override"] != null)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
}
}
ダニの答えがすでにあることは知っていますが、同様の問題を解決しようとしていたときに this (- http://blog.inedo.com/2010/10/12/http-418 -im-a-teapot-finally-a-%e2%80%9clegitimate%e2%80%9d-use / )代替手段として。
基本的に、コードで独自のHTTPステータスコード(たとえば、418)を返します。私の場合、WCFデータサービス。
throw new DataServiceException(418, "401 Unauthorized");
次に、HTTPモジュールを使用してEndRequest
イベントで処理し、コードを401に書き直します。
HttpApplication app = (HttpApplication)sender;
if (app.Context.Response.StatusCode == 418)
{
app.Context.Response.StatusCode = 401;
}
ブラウザ/クライアントは正しいコンテンツとステータスコードを受け取ります。それは私にとっては素晴らしいことです:)
HTTPステータスコード418の詳細については、 この質問と回答 をご覧ください。
401をインターセプトしてリダイレクトを実行するフォーム認証については正しいことがわかりましたが、それを元に戻すこともできます。
基本的に必要なのは、ログインページへの302リダイレクトをインターセプトし、それを401に戻すhttpモジュールです。
それを行う手順は here で説明されています
指定されたリンクはWCFサービスに関するものですが、すべてのフォーム認証シナリオで同じです。
上記のリンクで説明したように、httpヘッダーもクリアする必要がありますが、元の応答(つまり、インターセプトする前)にCookieが含まれていた場合は、必ずCookieヘッダーを応答に戻す必要があります
これは既知の問題であり、そのための NuGetパッケージ および/または ソースコード が利用可能です。
表示するコードにWWW-Authenticate
ヘッダーを設定しないため、クライアントはフォーム認証ではなくHTTP認証を実行できません。この場合、401の代わりに403を使用する必要があり、FormsAuthenticaitonModule
によってインターセプトされません。
.NET Framework> = v4.0であるが<v4.5を使用している場合、面白いハック。 reflectionを使用して、アクセスできないSuppressFormsAuthenticationRedirect
プロパティの値を設定します。
// Set property to "true" using reflection
Response
.GetType()
.GetProperty("SuppressFormsAuthenticationRedirect")
.SetValue(Response, true, null);