ASP.NET MVCアプリケーションのAJAX要求認証の問題の正しい処理

最新のWebアプリケーションでは、ユーザーインターフェイスを作成するときにAJAXを使用することが標準になりました。 ただし、このため、場合によっては追加の問題が発生します。 多くの場合、これらの問題は、認証およびクライアントでのそのようなリクエストの処理プロセスに関連しています。



問題



jQueryを使用するWebアプリケーションがサーバーにアクセスし、そこからJSONの形式でデータを受信するとします。



サーバー:



  [HttpPost]
パブリックActionResult GetData()
 {

	 Jsonを返す(新規
	 {
		アイテム=新規[]
		 {
			 「李陳」、
			 「アブドラ・カミール」、
			 「Mark Schrenberg」、
			 「ケイティ・サリバン」、
			 「エリコガントマロ」、
		 }
	 });
 } 




顧客:



  var $ list = $( "#list");
 var $ status = $( "#status");
 $ list.empty();
 $ status.text( "読み込み中...");

 $ .post( "/ home / getdata")
	 .always(関数(){
		 $ status.empty();
	 })
	 .success(関数(データ){
		 for(var i = 0; i <data.Items.length; i ++){
			 $ list.append($( "<li />")。text(data.Items [i]));
		 }
	 }); 




ロジックは非常にシンプルで簡単です。 次に、アプリケーションに認証メカニズムを追加します。 これでも、すべてが非常に簡単です。ASP.NETMVCの古き良きFormsAuthenticationメカニズムとAuthorize属性を使用します。 コントローラーのコードは次のように変更されます。



  [HttpPost]
 [許可]
パブリックActionResult GetData()
 {
	 return Json(new
	 {
		アイテム=新規[]
		 {
			 「李陳」、
			 「アブドラ・カミール」、
			 「Mark Schrenberg」、
			 「ケイティ・サリバン」、
			 「エリコガントマロ」、
		 }
	 });
 } 




繰り返しますが、このコードに問題はありません。 認証後、ユーザーにはページが表示され、以前と同様にデータを受信できます。 ただし、問題は、(ユーザーの非アクティブが原因で)認証時間が切れたり、ユーザーがログアウトした場合(たとえば、別のブラウザータブで)、サーバーがコントローラーへの呼び出しに応答してHTTP 302 Foundを返すことです。







明らかに、この場合のクライアントコードは、このようなイベントの変化を予期していなかったため、アプリケーションが正しく動作しません。



理由



問題を解決する前に、理由を見てみましょう-HTTP 302はどこから来たのですか? どうやら-302はどこから来たのでしょうか? HTTP 401があると仮定するのは論理的です 。実際、FormsAuthenticationを使用すると、FormsAuthenticationModule(グローバル構成ファイルのHTTPモジュールのリストに追加される)が舞台裏で動作します。 このモジュールの内部を見ると、HTTPの現在のコードが401である場合、リダイレクト、つまり 302に置き換えます。







これは、要求が正常に処理されず、パスワードが必要な場合(戻りコード-401)にユーザーをパスワード入力ページにリダイレクトするために行われたと推測するのは簡単です。 この場合、ユーザーにはわかりやすいパスワード入力フォームが表示され、エラーコードのあるIISページは表示されません。 理にかなっていますよね?



ASP.NET MVCアプリケーションの観点から見ると、チェーンは次のようになります。



  1. 要求はアプリケーションに送られ、 AuthorizeAttributeフィルターでつまずきます。
  2. ユーザーは認証されていないため、このフィルターはHTTP 401を返します。これは論理的です(リフレクターを使用してこのフィルターの実装を確認することで、かなり簡単に確認できます)。
  3. それでは、FormsAuthenticationModuleが機能し、401をリダイレクトに置き換えます。




その結果、パスワードで保護されたページにアクセスすると、パスワード入力フォームが表示されます( これは良い )が、AJAXを介して同様のリソースにアクセスすると、この答えは情報になりません( 悪い )。



解決策



だからあなたは問題を解決するために必要なもの-



  1. サーバーがAJAX要求に対して401/403を、通常の要求に対して302を与えたこと。
  2. クライアント401/403で処理します。




正直なところ、FormsAuthenticationModuleは要求を401に置き換えないように強制できます。このため、HttpResponseには特別なプロパティSuppressFormsAuthenticationRedirectがあります。







唯一の質問は、どの場合にこのプロパティを変更するかであり、これは重要なことですが、誰がこれを正確に行うのでしょうか?



この質問に答える前に、AJAXリクエストへの応答でエラーのあるHTTPレスポンスを受信したときにクライアントがどのように応答するかに注意を払ってみましょう。 次の2つのシナリオがあります。



  1. ユーザーはシステム内でまったく認証されず( 401 )、パスワードloginを使用してページに送信する必要があります。
  2. ユーザーは認証されますが、このアクションはまだ利用できません( 403 )。 たとえば、ユーザーがメンバーではない特定のロールに対して何らかのアクションを許可できます。 それをパスワード入力ページに送信するのはおそらく愚かです-この場合、彼に十分な権限がないことを単に知らせるだけで十分です。




したがって、2つの状況を別々に処理する必要があります。 リフレクターを使用してAuthorizeAttributeをもう一度見てください。







...すなわち 常に401を返します。



良くない したがって、標準の動作をわずかに修正する必要があります。 それでは始めましょう。



まず 、現在のリクエストがAJAXリクエストであるかどうかを判断し、そうであれば、パスワード入力ページへのリダイレクトを無効にします。



 パブリッククラスApplicationAuthorizeAttribute:AuthorizeAttribute
 {
	 protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
	 {
		 var httpContext = filterContext.HttpContext;
		 var request = httpContext.Request;
		 var response = httpContext.Response;

		 if(request.IsAjaxRequest())
			 response.SuppressFormsAuthenticationRedirect = true;

		 base.HandleUnauthorizedRequest(filterContext);
	 }
 } 




2番目 -条件を追加します。ユーザーが認証された場合は403、そうでない場合は401を指定します。



 パブリッククラスApplicationAuthorizeAttribute:AuthorizeAttribute
 {
	 protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
	 {
		 var httpContext = filterContext.HttpContext;
		 var request = httpContext.Request;
		 var response = httpContext.Response;
		 var user = httpContext.User;

		 if(request.IsAjaxRequest())
		 {
			 if(user.Identity.IsAuthenticated == false)
				 response.StatusCode =(int)HttpStatusCode.Unauthorized;
			他に
				 response.StatusCode =(int)HttpStatusCode.Forbidden;

			 response.SuppressFormsAuthenticationRedirect = true;
			 response.End();
		 }

		 base.HandleUnauthorizedRequest(filterContext);
	 }
 } 




新しいフィルターの準備ができました。 ここで、標準のAuthorizeAttributeの代わりに、アプリケーションで作成したフィルターを使用する必要があります。 これは、麻酔薬にとって大きな欠陥のように思えるかもしれませんが、他に方法はありません。 解決策があれば、コメントでそれを見てうれしいです。



最後に、クライアントで401/403処理を追加します。 これを回避するために、リクエストごとに、jQueryでajaxErrorハンドラーを使用できます。



  $(ドキュメント).ajaxError(関数(e、xhr){
	 if(xhr.status == 401)
		 window.location = "/アカウント/ログイン";
	 else if(xhr.status == 403)
		 alert(「このリソースを要求するための十分な権限がありません。」);
 }); 




最後に-







マイナス-標準のフィルターではなく、新しいApplicationAuthorizeAttributeフィルターを使用する必要があります。 したがって、標準コードがすでにコードで使用されている場合、このコードはすべての場所で変更する必要があります。



アプリケーションのソースコードはgithubにあります。



All Articles