@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的な動作。
それほどコードを書くわけでも無いので、コレでいいかなーって思いますが、いかがでしょーか?後は、素直に別サイトにしてしまう、とか...。