@jsakamotoさんへ
そりゃそーですね!Apiなんだから。FormsAuthenticationの認証チケットを利用してWCFでもForm認証を使う、っていう話が前にありましたが、それはそれで王道なやり方じゃない、ですよね。APIとして認証するならOAuthとかなんでしょうかね。その場合の受けはHttpMessageHandler使ってやればいいのかな。
とはいえ今回は受けじゃなくてレスポンスのですしたね。
普通にAuthorize属性使うと401になるから問題無さそうだけど、MVC(WebFormsでも一緒)アプリケーションに組み込んだ場合、Form認証も入ってたりするはずなので、FormsAuthenticationModuleに途中でレスポンスコードを横取りされた結果、ログインページへのリダイレクト(302)になってしまう。そうなると、クライアントがブラウザならいいけど、APIを呼び出してるプログラムだったら、そんな~、ログインページとか困ります~、です。
Web APIの標準テンプレートを使った場合に、以下のようにHomeControllerとValuesControllerを書き換えたとします。
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Login()
{
return Content("ログインしてね!");
}
[Authorize]
public ActionResult AccessDenied()
{
return Content("About");
}
}
[Authorize]
public class ValuesController : ApiController
{
// GET /api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
:
:
}
ログインページは以下のように変更。
<forms loginUrl="~/Home/Login" timeout="2880" />
そうするとApiControllerにAuthorizeつけてブラウザでアクセスすると「ログインしてね!」が表示されます。ステータスコードは302のあとページ表示で200。
コラー!
なので、FormsAuthenticationModuleに書き換えられたステータスをカスタムモジュールで再度書き換えました。
using System;
using System.Web;
using System.Web.Http.WebHost;
using System.Web.Security;
namespace Mvc4BApi
{
public class CustomAuthenticationModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_EndRequest(object sender, EventArgs e)
{
var application = sender as HttpApplication;
var response = application.Response;
if (!(application.Context.CurrentHandler is HttpControllerHandler))
return;
if(response.StatusCode == 302 && response.RedirectLocation.StartsWith(FormsAuthentication.LoginUrl))
{
response.ClearHeaders();
response.ClearContent();
response.StatusCode = 401;
application.CompleteRequest();
}
}
}
}
うぬ。Web.configもモジュール使うようにしましょう。
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true">
<add name="customauth" type="Mvc4BApi.CustomAuthenticationModule" />
</modules>
</system.webServer>
そうすると、どーなるかっていうと。
ステータスは401で中身なしになりました。
予め用意しといたAuthorize属性のついた/home/accessdeniedにアクセスすると。
ちゃんとForm認証と同じ挙動ですね。ログインページにリダイレクト。
FormsAuthenticationModuleが仕込まれてると、これはもうHttpHandlerの実行タイミングじゃどうにもならないのtで、こういうやり方になると思います。標準で用意されてる認証モジュールって共存出来ないし。
ApiControllerはHttpControllerHandlerから実行されるので、実質HttpHandlerですよね。途中でMessageをいじるっていうのはそこに対するAOP的な動作。
それほどコードを書くわけでも無いので、コレでいいかなーって思いますが、いかがでしょーか?後は、素直に別サイトにしてしまう、とか...。