jQuery Ajax呼び出し後のリダイレクト要求を管理する方法


1369

私は$.post()Ajax を使用してサーブレットを呼び出し、次に結果のHTMLフラグメントを使用divしてユーザーの現在のページの要素を置き換えています。ただし、セッションがタイムアウトした場合、サーバーはリダイレクトディレクティブを送信してユーザーをログインページに送ります。この場合、jQueryはdiv要素をログインページのコンテンツに置き換えているため、ユーザーの目は実際には珍しいシーンを目撃しています。

jQuery 1.2.6でAjax呼び出しからのリダイレクトディレクティブを管理するにはどうすればよいですか?


1
(そのような答えではありません)-私は過去にjqueryライブラリを編集し、各XHR完了時にログインページのチェックを追加することでこれを行いました。アップグレードするたびに実行する必要があるため、最善の解決策ではありませんが、問題は解決します。
Sugendran 2008年

1
関連の質問を参照してください:stackoverflow.com/questions/5941933/...
Nutel

HttpContext.Response.AddHeaderajaxsetup成功事例の時とチェック進むべき道である
LCJ

4
サーバーが401を返さないのはなぜですか?その場合、グローバル$ .ajaxSetupを設定し、ステータスコードを使用してページをリダイレクトできます。
Vishal

1
このリンクdoanduyhai.wordpress.com/2012/04/21/…私に正しい解決策を提供します
pappu_kutty '19

回答:


697

私はこの質問を読み、ブラウザがリダイレクトを透過的に処理することを回避するために、応答HTTPステータスコードを278に設定することに関して述べられているアプローチを実装しました。これはうまくいきましたが、少しハックなので少し不満でした。

さらに掘り下げた後、私はこのアプローチを捨て、JSONを使用しました。この場合、AJAX要求に対するすべての応答のステータスコードは 200で、応答の本文にはサーバー上で構築されたJSONオブジェクトが含まれています。その後、クライアントのJavaScriptはJSONオブジェクトを使用して、何をする必要があるかを決定できます。

私はあなたと同じような問題を抱えていました。2つの可能な応答があるAJAX要求を実行します。1つはブラウザーを新しいページにリダイレクトするもので、もう1つは現在のページの既存のHTMLフォームを新しいものに置き換えるものです。これを行うjQueryコードは次のようになります。

$.ajax({
    type: "POST",
    url: reqUrl,
    data: reqBody,
    dataType: "json",
    success: function(data, textStatus) {
        if (data.redirect) {
            // data.redirect contains the string URL to redirect to
            window.location.href = data.redirect;
        } else {
            // data.form contains the HTML for the replacement form
            $("#myform").replaceWith(data.form);
        }
    }
});

JSONオブジェクト「データ」は、サーバー上に構築され、2つのメンバー:data.redirectとを持ちdata.formます。このアプローチの方がはるかに優れていることがわかりました。


58
stackoverflow.com/questions/503093/のソリューションに記載されているように、window.location.replace(data.redirect);を使用することをお勧めします。window.location.href = data.redirectよりも
CarlesBarrobés

8
アクションでHTTPコードを使用する方が良いと思われない理由。たとえば、HTTP Temporary Redirectである307コードは?
Sergei Golos、2011

15
@Sergei Golosの理由は、HTTPリダイレクトを行うと、リダイレクトが実際にはajax成功コールバックに到達しないことです。ブラウザはリダイレクトを処理し、リダイレクトの宛先のコンテンツを含む200コードを配信します。
Miguel Silva、

2
サーバーコードはどのようになりますか?data.formとdata.redirectの戻り値を持つようにします。基本的に、それにリダイレクトを入れるかどうかをどのように判断しますか?
Vnge

6
この答えは、サーバーでこれがどのように行われるかを示していれば、より役に立ちます。
Pedro Hoehl Carvalho、2015

243

私はこの問題を次の方法で解決しました:

  1. 応答にカスタムヘッダーを追加します。

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. JavaScript関数をajaxSuccessイベントにバインドし、ヘッダーが存在するかどうかを確認します。

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });

6
なんて素晴らしいソリューションでしょう。ワンストップソリューションのアイデアが気に入っています。私は403ステータスをチェックする必要がありますが、このためにajaxSuccess bind on bodyを使用できます(これは私が本当に探していたものです)。ありがとうございます。
Bretticus

4
私はこれを実行したところ、$。get()関数を使用していて、200以外のステータスが発生していなかったajaxCompleteが必要であることがわかりました。実際、おそらく代わりにajaxErrorにバインドすることもできます。詳細については、以下の私の回答を参照してください。
Bretticus

2
多くの場合問題ありませんが、フレームワークが承認を処理する場合はどうでしょうか?
jwaliszko

13
私はヘッダーアプローチが好きですが、@ mwoods79のように、リダイレクト先の知識は重複してはいけないと考えています。ブール値の代わりにヘッダーREDIRECT_LOCATIONを追加することで解決しました。
rintcius

3
リダイレクト後の応答にヘッダーを設定するように注意してください。このページの他の回答で説明されているように、リダイレクトはajaxSuccesハンドラーに対して透過的です。したがって、ログインページのGET応答にヘッダーを含めました(これは、結局のところ、私のシナリオではajaxSuccessをトリガーするだけでした)。
sieppl

118

301と302の応答を正しく処理するブラウザーはありません。実際、この規格では、それらを「透過的に」処理する必要があるとさえ言われており、これはAjaxライブラリベンダーにとって大きな頭痛の種です。ではRA-アヤックス我々は透過的に扱うサーバーからリダイレクトするHTTP応答ステータスコード278(ほんの一部の「未使用」の成功コード)を使用を余儀なくされました...

これは本当に私を困らせます、そして、ここの誰かがW3Cにいくらかの「プル」を持っているなら、私たちはあなたが私たちが本当に301と302コードを自分で処理する必要があることをW3Cに知らせていただければ幸いです...!;)


3
私は278が公式のHTTP仕様の一部になるべきであるという1つの動きについてです。
Chris Marisic

2
すでに透過的に処理していませんか?リソースが移動した場合、リソースを透過的に処理するということは、提供されたURLで要求を繰り返すことを意味します。それが、XMLHttpRequest APIを使用して期待することです。
PhilippeRathé2013

@PhilippeRathé同意する。透過的に処理することは、まさに私が望んでいることです。そして、なぜそれが悪いと考えられているのか分かりません。
smwikipedia

@smwikipediaページをリダイレクトせずに、メインセクションでマークアップのリダイレクトを調整します。
cmc

ここの誰かがW3Cに「プル」がある場合は、301および302コードを自分で処理する必要があることをW3Cに知らせていただければ幸いです...!;)
Tommy.Tang

100

最終的に実装されたソリューションは、Ajax呼び出しのコールバック関数にラッパーを使用し、このラッパーで返されたHTMLチャンクに特定の要素が存在するかどうかを確認することでした。要素が見つかった場合、ラッパーはリダイレクトを実行しました。そうでない場合、ラッパーは実際のコールバック関数に呼び出しを転送しました。

たとえば、ラッパー関数は次のようなものでした。

function cbWrapper(data, funct){
    if($("#myForm", data).length > 0)
        top.location.href="login.htm";//redirection
    else
        funct(data);
}

次に、Ajax呼び出しを行うときに、次のようなものを使用しました。

$.post("myAjaxHandler", 
       {
        param1: foo,
        param2: bar
       },
       function(data){
           cbWrapper(data, myActualCB);
       }, 
       "html"
);

すべてのAjax呼び出しが、ページの一部を置き換えるために使用するDIV要素内に常にHTMLを返したため、これはうまくいきました。また、ログインページにリダイレクトするだけで済みました。


4
これは関数cbWrapper(funct){return function(data){if($( "#myForm"、data).size()> 0)top.location.href = "login";に短縮できることに注意してください。他のfunct(data); }}。その後、.postを呼び出すときにのみcbWrapper(myActualCB)が必要になります。はい、コメント内のコードはごちゃごちゃですが、注意する必要があります:)
Simen Echholt 2010年

ここではサイズの代わりに.lengthを使用できるので、サイズは減価しています
sunil '20

66

レモンを少しひねったティメルツの方法が好きです。JSONを期待しているときにcontent / textのcontentTypeが返される場合は、リダイレクトされている可能性が高いです。私の場合、ページをリロードするだけで、ログインページにリダイレクトされます。おっと、jqXHRのステータスが200であることを確認してください。これは、エラー関数にいるため、おかしいようです。それ以外の場合、正当なエラーの場合は、繰り返しの再読み込みを強制します(おっと)

$.ajax(
   error:  function (jqXHR, timeout, message) {
    var contentType = jqXHR.getResponseHeader("Content-Type");
    if (jqXHR.status === 200 && contentType.toLowerCase().indexOf("text/html") >= 0) {
        // assume that our login has expired - reload our current page
        window.location.reload();
    }

});

1
ブライアンさん、ありがとうございます。私のシナリオにはあなたの答えが最も適していましたが、単純な「コンテンツタイプ」チェックの代わりに、リダイレクト先のURL /ページを比較するなど、より安全なチェックがあるとよいのですが。jqXHRオブジェクトからリダイレクトされているページを見つけることができませんでした。
ジョニー

ステータス401を確認してからリダイレクトします。チャンピオンのように機能します。
エリック

55

低レベルの$.ajax()呼び出しを使用します。

$.ajax({
  url: "/yourservlet",
  data: { },
  complete: function(xmlHttp) {
    // xmlHttp is a XMLHttpRquest object
    alert(xmlHttp.status);
  }
});

リダイレクトのためにこれを試してください:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}

私は低レベルのものを避けようとしていました。とにかく、私があなたが説明したようなものを使用している場合、HTTPコードが3xxであることを検出したら、ブラウザのリダイレクトを強制するにはどうすればよいですか?私の目標は、ユーザーのセッションが期限切れになったことを知らせるだけでなく、ユーザーをリダイレクトすることです。
Elliot Vargas、

4
ところで、$。ajax()は非常に低レベルではありません。$ .ajaxやそのすべてのオプションよりもはるかに単純な$ .get、$。postなどがあるため、jQueryの観点から見ると低レベルです。
まで

16
ああ少年!申し訳ありませんが、あなたの答えを「受け入れない」必要がありましたが、それでも非常に役に立ちます。ことですが、リダイレクトはXMLHttpRequestによって自動的に管理されるため、リダイレクト後は常に200のステータスコードを取得します(ため息!)。HTMLを解析してマーカーを探すような厄介なことをする必要があると思います。
Elliot Vargas、

1
気になるのは、セッションがサーバーで終了する場合、サーバーが別のSESSIONIDを送信することを意味するのではないですか。それを検出することはできませんか?
Salamander2007

10
注:これはリダイレクトでは機能しません。ajaxは新しいページに移動し、そのステータスコードを返します。

33

これが誰かの役に立つかもしれないので、私は私のアプローチを共有したかっただけです:

私は基本的に、ユーザー名の表示や、この場合はログインページへのリダイレクトを処理するなどの認証を処理するJavaScriptモジュールを含めました。

私のシナリオ:基本的に、すべての要求をリッスンし、302とロケーションヘッダーでログインページに応答するISAサーバーがあります。

私のJavaScriptモジュールでは、最初のアプローチは次のようなものでした

$(document).ajaxComplete(function(e, xhr, settings){
    if(xhr.status === 302){
        //check for location header and redirect...
    }
});

問題(ここで既に述べたように)は、ブラウザーがリダイレクトを単独で処理するため、ajaxCompleteコールバックが呼び出されず、代わりに、既にリダイレクトされたログインページの応答が返されましたstatus 200。問題:成功した200応答が実際のログインページなのか、その他の任意のページなのかをどのように検出しますか?

ソリューション

302リダイレクト応答をキャプチャできなかったのでLoginPage、ログインページ自体のURLを含むヘッダーをログインページに追加しました。モジュールで、ヘッダーをリッスンしてリダイレクトを行います。

if(xhr.status === 200){
    var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage");
    if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){
        window.location.replace(loginPageRedirectHeader);
    }
}

...そしてそれは魅力のように機能します:)。なぜLoginPageヘッダーにURLを含めるのか不思議に思うかもしれません...基本的にGETxhrオブジェクトからの自動ロケーションリダイレクトの結果のURLを特定する方法が見つからなかったためです...


+1-ただし、カスタムヘッダーはで始まることが想定されているX-ため、より適切なヘッダーを使用しますX-LoginPage: http://example.com/login
uınbɐɥs

6
@ShaquinTrifonoffもうありません。2011年6月にITEFドキュメントで非推奨が提案されたため、X-プレフィックスを使用しませんでした。実際、2012年6月以降、カスタムヘッダーにプレフィックスを付ける必要はありませんX-
ジュリ

ISAサーバーもあり、同じ問題が発生しました。コードで回避するのではなく、kb2596444の指示を使用して、リダイレクトを停止するようにISAを構成しました。
スコットストーン2013

29

私はこのトピックが古いことを知っていますが、私が見つけ、以前にここで説明し別のアプローチを紹介します。基本的に私はWIFでASP.MVCを使用しています(ただし、これはこのトピックのコンテキストではそれほど重要ではありません-どのフレームワークを使用しても回答は適切です。 )

以下に示すアプローチは、そのままの状態ですべてのajaxリクエストに適用できます(beforeSendイベントを明らかに再定義しない場合)。

$.ajaxSetup({
    beforeSend: checkPulse,
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        document.open();
        document.write(XMLHttpRequest.responseText);
        document.close();
    }
});

ajaxリクエストが実行される前に、CheckPulseメソッドが呼び出されます(最も単純なコントローラーメソッド)。

[Authorize]
public virtual void CheckPulse() {}

ユーザーが認証されていない(トークンの有効期限が切れている)場合、そのようなメソッドにはアクセスできません(Authorize属性で保護されています)。フレームワークは認証を処理するため、トークンの有効期限が切れている間、応答にhttpステータス302を設定します。ブラウザーが302応答を透過的に処理しないようにするには、Global.asaxでそれをキャッチし、応答ステータスを変更します(例:200 OK)。さらに、ヘッダーを追加して、そのような応答を特別な方法で(後でクライアント側で)処理するように指示します。

protected void Application_EndRequest()
{
    if (Context.Response.StatusCode == 302
        && (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
    {                
        Context.Response.StatusCode = 200;
        Context.Response.AddHeader("REQUIRES_AUTH", "1");
    }
}

最後に、クライアント側でそのようなカスタムヘッダーを確認します。存在する場合-ログオンページへの完全なリダイレクトを実行する必要があります(私の場合window.locationは、フレームワークによって自動的に処理される要求からのURLに置き換えられます)。

function checkPulse(XMLHttpRequest) {
    var location = window.location.href;
    $.ajax({
        url: "/Controller/CheckPulse",
        type: 'GET',
        async: false,
        beforeSend: null,
        success:
            function (result, textStatus, xhr) {
                if (xhr.getResponseHeader('REQUIRES_AUTH') === '1') {
                    XMLHttpRequest.abort(); // terminate further ajax execution
                    window.location = location;
                }
            }
    });
}

EndRequestイベントの代わりにPostAuthenticateRequestイベントを使用して問題を修正しました。
Frinavale 2013年

@JaroslawWaliszko私は最後の返信に間違ったイベントを貼り付けました!PreSendRequestHeadersイベントを意味しました..... PostAuthenticateRequestではありません!>> blush <<私の間違いを指摘してくれてありがとう。
Frinavale 2013年

@JaroslawWaliszko WIFを使用する場合は、AJAXリクエストに対して401応答を返し、JavaScriptでこれらを処理することもできます。また、すべての302が真であるとは限らない認証が必要であると想定しています。興味があれば回答を追加しました。
ロブ

26

これを処理するより良い方法は、特に既存のHTTPプロトコル応答コードを活用することだと思います401 Unauthorized

ここに私がそれを解決した方法があります:

  1. サーバー側:セッションの有効期限が切れ、リクエストがajaxの場合。401応答コードヘッダーを送信する
  2. クライアント側:ajaxイベントにバインドする

    $('body').bind('ajaxSuccess',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    }).bind('ajaxError',function(event,request,settings){
    if (401 == request.status){
        window.location = '/users/login';
    }
    });

IMOこれはより一般的で、新しいカスタム仕様/ヘッダーを作成していません。また、既存のajax呼び出しを変更する必要はありません。

編集: @Robの以下のコメントによれば、401(認証エラーのHTTPステータスコード)が指標になるはずです。参照してください403 401の不正なHTTPレスポンス対禁止を。そうは言っても、一部のWebフレームワークは認証と承認の両方のエラーに403を使用するため、それに応じて調整してください。ロブに感謝します。


私は同じアプローチを使用します。jQueryは本当に403エラーコードでajaxSuccessを呼び出しますか?私はajaxError一部が実際に必要だと思う
マリウスBalčytis

24

私はこの問題を次のように解決しました:

応答を処理するミドルウェアを追加します。それがajax要求のリダイレクトである場合は、リダイレクトURLを使用して応答を通常の応答に変更します。

class AjaxRedirect(object):
  def process_response(self, request, response):
    if request.is_ajax():
      if type(response) == HttpResponseRedirect:
        r = HttpResponse(json.dumps({'redirect': response['Location']}))
        return r
    return response

次にajaxCompleteで、応答にリダイレクトが含まれている場合、それはリダイレクトである必要があるため、ブラウザーの場所を変更します。

$('body').ajaxComplete(function (e, xhr, settings) {
   if (xhr.status == 200) {
       var redirect = null;
       try {
           redirect = $.parseJSON(xhr.responseText).redirect;
           if (redirect) {
               window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
           }
       } catch (e) {
           return;
       }
   }
}

20

私は私のために働く簡単な解決策を持っています、サーバーコードの変更は必要ありません...ナツメグの小さじを追加してください...

$(document).ready(function ()
{
    $(document).ajaxSend(
    function(event,request,settings)
    {
        var intercepted_success = settings.success;
        settings.success = function( a, b, c ) 
        {  
            if( request.responseText.indexOf( "<html>" ) > -1 )
                window.location = window.location;
            else
                intercepted_success( a, b, c );
        };
    });
});

私はhtmlタグの存在を確認しますが、indexOfを変更して、ログインページに存在する一意の文字列を検索できます...


これは私にはうまくいかないようです、それはajax呼び出しで定義された関数を呼び出し続けます、それはそれがsuccessメソッドをオーバーライドしていないようなものです。
adriaanp

少なくとももううまくいかないようです。ここで考えられる説明:stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal

20

私が見つけた別の解決策(特にグローバルな動作を設定したい場合に特に便利です)は、プロパティと一緒に$.ajaxsetup()メソッドを使用することです。他の人が指摘したように、リダイレクトステータスコード()を使用しないでください。代わりに、ステータスコードを使用してリダイレクトクライアント側を処理します。statusCode3xx4xx

$.ajaxSetup({ 
  statusCode : {
    400 : function () {
      window.location = "/";
    }
  }
});

400処理するステータスコードに置き換えます。すでに述べた401 Unauthorizedように良いアイデアかもしれません。私はを使用し400ていますが、これは非常に不特定であり401、より具体的なケース(間違ったログイン資格情報など)に使用できます。したがって、直接リダイレクトする代わり4xxに、セッションがタイムアウトしてクライアント側でリダイレクトを処理すると、バックエンドはエラーコードを返すはずです。backbone.jsのようなフレームワークでも完璧に機能します


ページの関数についてどこに言及しますか?
Vikrant 2017年

@Vikrant私があなたの質問を正しく理解していれば、jQueryがロードされた直後で、実際のリクエストを行う前に関数を呼び出すことができます。
morten.c 2017年

19

与えられたソリューションのほとんどは、追加のヘッダーまたは不適切なHTTPコードを使用して、回避策を使用します。これらのソリューションはおそらく機能しますが、少し「ハッキー」に感じます。私は別の解決策を考え出しました。

401応答でリダイレクトするように構成された(passiveRedirectEnabled = "true")WIFを使用しています。リダイレクトは通常のリクエストを処理するときに役立ちますが、AJAXリクエストに対しては機能しません(ブラウザーが302 /リダイレクトを実行しないため)。

global.asaxで次のコードを使用すると、AJAXリクエストのリダイレクトを無効にできます。

    void WSFederationAuthenticationModule_AuthorizationFailed(object sender, AuthorizationFailedEventArgs e)
    {
        string requestedWithHeader = HttpContext.Current.Request.Headers["X-Requested-With"];

        if (!string.IsNullOrEmpty(requestedWithHeader) && requestedWithHeader.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
        {
            e.RedirectToIdentityProvider = false;
        }
    }

これにより、AJAXリクエストに対して401応答を返すことができ、JavaScriptはページをリロードすることで処理できます。ページをリロードすると401がスローされ、WIFによって処理されます(WIFはユーザーをログインページにリダイレクトします)。

401エラーを処理するJavaScriptの例:

$(document).ajaxError(function (event, jqxhr, settings, exception) {

    if (jqxhr.status == 401) { //Forbidden, go to login
        //Use a reload, WIF will redirect to Login
        location.reload(true);
    }
});

良い解決策。ありがとう。
DanMan 2018年

19

この問題は、ASP.NET MVC RedirectToActionメソッドを使用すると発生する可能性があります。フォームが応答をdivに表示しないようにするには、$。ajaxSetupを使用して、着信応答に対して何らかのajax応答フィルターを実行するだけです。応答にMVCリダイレクトが含まれている場合、JS式でこの式を評価できます。以下のJSのコード例:

$.ajaxSetup({
    dataFilter: function (data, type) {
        if (data && typeof data == "string") {
            if (data.indexOf('window.location') > -1) {
                eval(data);
            }
        }
        return data;
    }
});

データが:「window.location = '/ Acount / Login'」の場合、上記のフィルターはそれをキャッチし、データを表示させる代わりに評価してリダイレクトを作成します。


data応答の本文またはヘッダーにありますか?
GMsoF 2017年

16

ウラジミール・プルドニコフとトーマス・ハンセンが言ったことをまとめる:

  • サーバー側のコードを変更して、XHRかどうかを検出します。そうであれば、リダイレクトの応答コードを278に設定します。djangoの場合:
   if request.is_ajax():
      response.status_code = 278

これにより、ブラウザーは応答を成功として扱い、JavaScriptに渡します。

  • JSで、フォームの送信がAjax経由であることを確認し、応答コードを確認して、必要に応じてリダイレクトします。
$('#my-form').submit(function(event){ 

  event.preventDefault();   
  var options = {
    url: $(this).attr('action'),
    type: 'POST',
    complete: function(response, textStatus) {    
      if (response.status == 278) { 
        window.location = response.getResponseHeader('Location')
      }
      else { ... your code here ... } 
    },
    data: $(this).serialize(),   
  };   
  $.ajax(options); 
});

13
    <script>
    function showValues() {
        var str = $("form").serialize();
        $.post('loginUser.html', 
        str,
        function(responseText, responseStatus, responseXML){
            if(responseStatus=="success"){
                window.location= "adminIndex.html";
            }
        });     
    }
</script>

12

試す

    $(document).ready(function () {
        if ($("#site").length > 0) {
            window.location = "<%= Url.Content("~") %>" + "Login/LogOn";
        }
    });

ログインページに配置します。メインページのdivに読み込まれた場合は、ログインページにリダイレクトされます。「#site」は、ログインページを除くすべてのページにあるdivのIDです。


12

Spring Securityを使用している場合、回答はうまくいくようですが、LoginUrlAuthenticationEntryPointを拡張し、AJAXをより堅牢に処理するための特定のコードを追加しました。ほとんどの例はすべてを傍受します認証の失敗だけでなく、リダイレクトを。これは私が取り組んでいるプロジェクトには望ましくありませんでした。失敗したAJAXリクエストをキャッシュしたくない場合は、ExceptionTranslationFilterを拡張して「sendStartAuthentication」メソッドをオーバーライドし、キャッシュ手順を削除する必要があるかもしれません。

AjaxAwareAuthenticationEntryPointの例:

public class AjaxAwareAuthenticationEntryPoint extends
    LoginUrlAuthenticationEntryPoint {

    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        if (isAjax(request)) {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself");
        } else {
        super.commence(request, response, authException);
        }
    }

    public static boolean isAjax(HttpServletRequest request) {
        return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }
}

出典: 12


3
反対票を投じた人がなぜ反対票を投じているのか説明してくれると助かります。この解決策に何か問題がある場合、私は自分の過ちから学びたいと思います。ありがとう。
John

Springを使用し、JSFも使用している場合は、これも確認してください:( "partial / ajax")。equalsIgnoreCase(request.getHeader( "faces-request"));
J Slick

あなたが言及しなかったため、ユーザーが反対票を投じた可能性があります。(1)エラー応答を検出するために必要なクライアント側の改造。(2)カスタマイズされたLoginUrlAuthenticationEntryPointをフィルターチェーンに追加するためにSpring構成に必要な変更。
J Slick

あなたの答えは@Arpadからのこれに似ています。それは私にとってはうまくいった。Spring Security 3.2.9を使用します。stackoverflow.com/a/8426947/4505142
Darren Parker

11

これを解決するには、login.phpページに次のように記述します。

<script type="text/javascript">
    if (top.location.href.indexOf('login.php') == -1) {
        top.location.href = '/login.php';
    }
</script>

10

@Stegで説明されている問題をもう一度引用しましょう

私はあなたと同じような問題を抱えていました。2つの可能な応答があるajaxリクエストを実行します。1つはブラウザーを新しいページにリダイレクトするもので、もう1つは現在のページの既存のHTMLフォームを新しいものに置き換えるものです。

私見これは実際の課題であり、現在のHTTP標準に正式に拡張する必要があります。

新しいHttp標準は、新しいステータスコードを使用することになると思います。意味:現在301/302、ブラウザにこのリクエストのコンテンツを新しいにフェッチするように指示しlocationます。

拡張規格では、応答status: 308(単なる例)の場合、ブラウザはメインページをlocation提供されたページにリダイレクトする必要があると述べています。

言われていること; 私はすでにこの将来の動作を模倣する傾向があるので、document.redirectが必要な場合、サーバーに次のように応答させます。

status: 204 No Content
x-status: 308 Document Redirect
x-location: /login.html

JSが「status: 204 "をと、x-status: 308ヘッダーの存在を確認し、ドキュメントで提供されているページにdocument.redirectを実行しますlocation

これはあなたにとって意味がありますか?


7

いくつかは以下が役に立つと思うかもしれません:

認証トークンなしで送信されるすべてのRESTアクションのログインページにクライアントをリダイレクトする必要がありました。私の残りのアクションはすべてAjaxベースであるため、Ajax成功関数を処理する代わりに、ログインページにリダイレクトするための優れた汎用的な方法が必要でした。

これは私がやったことです:

Ajaxリクエストに対して、サーバーはJSON 200応答「NEED TO AUTHENTICATE」を返します(クライアントが認証する必要がある場合)。

Javaの簡単な例(サーバー側):

@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    public static final String COOKIE_NAME = "token_cookie"; 

    @Override
    public void filter(ContainerRequestContext context) throws IOException {        
        // Check if it has a cookie.
        try {
            Map<String, Cookie> cookies = context.getCookies();

            if (!cookies.containsKey(COOKIE_NAME)) {
                m_logger.debug("No cookie set - redirect to login page");
                throw new AuthenticationException();
            }
        }
        catch (AuthenticationException e) {
            context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build());
        }
    }
}

私のJavaScriptに次のコードを追加しました:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    var originalSuccess = options.success;

    options.success = function(data) {
        if (data == "NEED TO AUTHENTICATE") {
            window.location.replace("/login.html");
        }
        else {
            originalSuccess(data);
        }
    };      
});

そして、それはそれについてです。


5

サーブレットで response.setStatus(response.SC_MOVED_PERMANENTLY); は、リダイレクトに必要な '301' xmlHttpステータスを送信する必要があります...

$ .ajax関数では、.toString()関数を使用しないでください...

if (xmlHttp.status == 301) { top.location.href = 'xxxx.jsp'; }

問題は、それがあまり柔軟ではなく、どこにリダイレクトするかを決定できないことです。

サーブレットを介したリダイレクトが最善の方法です。しかし、それでも正しい方法を見つけることができません。


5

私はページ全体に対するすべてのajaxリクエストにラッチをかけたかっただけです。@SuperGは私を始めました。これが私が終わったものです:

// redirect ajax requests that are redirected, not found (404), or forbidden (403.)
$('body').bind('ajaxComplete', function(event,request,settings){
        switch(request.status) {
            case 301: case 404: case 403:                    
                window.location.replace("http://mysite.tld/login");
                break;
        }
});

特定のHTTPステータスコードを具体的にチェックして、私の判断の基準にしたいと思いました。ただし、単にajaxErrorにバインドして、成功以外の何かを取得することができます(たぶん200だけ?)。

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}

1
後者は、トラブルシューティング
Tim Abell

1
403は、ユーザーが認証されていないことを意味するのではなく、(おそらく認証された)ユーザーが要求されたリソースを表示する権限を持っていないことを意味します。そのため、ログインページにリダイレクトしないでください
Rob

5

値も渡したい場合は、セッション変数を設定してEgにアクセスすることもできます。jspで次のように記述できます。

<% HttpSession ses = request.getSession(true);
   String temp=request.getAttribute("what_you_defined"); %>

そして、あなたはあなたのjavascript変数にこの一時的な値を保存して、遊ぶことができます


5

ヘッダーソリューションでは成功しませんでした-それらは私のajaxSuccess / ajaxCompleteメソッドで取得されませんでした。私はカスタム応答でStegの応答を使用しましたが、JS側をいくつか変更しました。標準$.get$.postメソッドを使用できるように、各関数で呼び出すメソッドを設定します。

function handleAjaxResponse(data, callback) {
    //Try to convert and parse object
    try {
        if (jQuery.type(data) === "string") {
            data = jQuery.parseJSON(data);
        }
        if (data.error) {
            if (data.error == 'login') {
                window.location.reload();
                return;
            }
            else if (data.error.length > 0) {
                alert(data.error);
                return;
            }
        }
    }
    catch(ex) { }

    if (callback) {
        callback(data);
    }
}

使用例...

function submitAjaxForm(form, url, action) {
    //Lock form
    form.find('.ajax-submit').hide();
    form.find('.loader').show();

    $.post(url, form.serialize(), function (d) {
        //Unlock form
        form.find('.ajax-submit').show();
        form.find('.loader').hide();

        handleAjaxResponse(d, function (data) {
            // ... more code for if auth passes ...
        });
    });
    return false;
}

5

最後に、カスタムを追加して問題を解決しますHTTP Header。サーバー側のすべての要求に対する応答の直前に、現在要求されているURLを応答のヘッダーに追加します。

サーバー上のアプリケーションの種類はAsp.Net MVCであり、それを行うのに適した場所です。でGlobal.asax私実装さApplication_EndRequestそうイベント:

    public class MvcApplication : System.Web.HttpApplication
    {

    //  ...
    //  ...

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            var app = (HttpApplication)sender;
            app.Context.Response.Headers.Add("CurrentUrl",app.Context. Request.CurrentExecutionFilePath);
        }

    }

それは私にぴったりです!今度は、JQuery $.postiのすべての応答で、要求さurlれたヘッダーとPOST、statusによるメソッドの結果として得られる他の応答ヘッダーがあります302303 ...。

その他の重要なことは、サーバー側でもクライアント側でもコードを変更する必要がないことです。

次は、このようにして、エラー、メッセージなどのポストアクションの他の情報にアクセスする機能です。

私はこれを投稿しました、誰かを助けるかもしれません:)


4

私は私がいじくり回しているdjangoアプリでこの問題を抱えていました(免責事項:私は学ぶためにいじくり回しており、決して専門家ではありません)。私がやりたかったのは、jQuery ajaxを使用してDELETE要求をリソースに送信し、サーバー側で削除してから、リダイレクトを(基本的に)ホームページに送り返すことでした。私HttpResponseRedirect('/the-redirect/')がpythonスクリプトから送信したとき、jQueryのajaxメソッドは302ではなく200を受信して​​いました。したがって、私がしたことは300の応答を送信することでした:

response = HttpResponse(status='300')
response['Location'] = '/the-redirect/' 
return  response

次に、jQuery.ajaxを使用してクライアントでリクエストを送信/処理しました。

<button onclick="*the-jquery*">Delete</button>

where *the-jquery* =
$.ajax({ 
  type: 'DELETE', 
  url: '/resource-url/', 
  complete: function(jqxhr){ 
    window.location = jqxhr.getResponseHeader('Location'); 
  } 
});

300を使用することは「適切」ではないかもしれませんが、少なくとも私が望んでいたとおりに機能しました。

PS:これはモバイルバージョンのSOで編集するのは大変な苦労でした。ばかげたISPが私のサービスキャンセルリクエストを処理しました。


4

XMLHttpRequest送信プロトタイプをフックすることもできます。これは、1つのハンドラーですべての送信(jQuery / dojo / etc)に対して機能します。

このコードは、500ページの期限切れエラーを処理するために記述しましたが、200リダイレクトをトラップするためにも機能するはずです。readyStateの意味について、XMLHttpRequest onreadystatechangeのウィキペディアエントリを準備します。

// Hook XMLHttpRequest
var oldXMLHttpRequestSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.send = function() {
  //console.dir( this );

  this.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 500 && this.responseText.indexOf("Expired") != -1) {
      try {
        document.documentElement.innerHTML = this.responseText;
      } catch(error) {
        // IE makes document.documentElement read only
        document.body.innerHTML = this.responseText;
      }
    }
  };

  oldXMLHttpRequestSend.apply(this, arguments);
}

2

さらに、おそらくユーザーをヘッダーで指定されたURLにリダイレクトする必要があります。最終的には次のようになります:

$.ajax({
    //.... other definition
    complete:function(xmlHttp){
        if(xmlHttp.status.toString()[0]=='3'){
        top.location.href = xmlHttp.getResponseHeader('Location');
    }
});

UPD:Opps。同じタスクを持っていますが、機能しません。このようなことをしています。見つけたら解決策を紹介します。


4
彼自身はこれがうまくいかないと言っているので、この答えは作者によって削除されるべきです。彼は後で作業ソリューションを投稿できます。-1
TheCrazyProgrammer 2017

アイデアは良いですが、問題は「Location」ヘッダーが渡されないことです。
MartinZvarík19年

編集が拒否されたので、「var xmlHttp = $ .ajax({....」を使用する必要があります。変数を内部に含めることはできません...次に使用します:console.log(xmlHttp.getAllResponseHeaders()) ;
MartinZvarík19年

2

@Johnおよび@Arpad リンクと@RobWinch からの回答を使用して、有効なソリューションを取得 しましたリンクを

Spring Security 3.2.9とjQuery 1.10.2を使用しています。

Springのクラスを拡張して、AJAXリクエストからのみ4XX応答を発生させます。

public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    public CustomLoginUrlAuthenticationEntryPoint(final String loginFormUrl) {
        super(loginFormUrl);
    }

    // For AJAX requests for user that isn't logged in, need to return 403 status.
    // For normal requests, Spring does a (302) redirect to login.jsp which the browser handles normally.
    @Override
    public void commence(final HttpServletRequest request,
                         final HttpServletResponse response,
                         final AuthenticationException authException)
            throws IOException, ServletException {
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
        } else {
            super.commence(request, response, authException);
        }
    }
}

applicationContext-security.xml

  <security:http auto-config="false" use-expressions="true" entry-point-ref="customAuthEntryPoint" >
    <security:form-login login-page='/login.jsp' default-target-url='/index.jsp'                             
                         authentication-failure-url="/login.jsp?error=true"
                         />    
    <security:access-denied-handler error-page="/errorPage.jsp"/> 
    <security:logout logout-success-url="/login.jsp?logout" />
...
    <bean id="customAuthEntryPoint" class="com.myapp.utils.CustomLoginUrlAuthenticationEntryPoint" scope="singleton">
        <constructor-arg value="/login.jsp" />
    </bean>
...
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
    <property name="requestMatcher">
      <bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
        <constructor-arg>
          <bean class="org.springframework.security.web.util.matcher.MediaTypeRequestMatcher">
            <constructor-arg>
              <bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
            </constructor-arg>
            <constructor-arg value="#{T(org.springframework.http.MediaType).APPLICATION_JSON}"/>
            <property name="useEquals" value="true"/>
          </bean>
        </constructor-arg>
      </bean>
    </property>
</bean>

私のJSPで、ここに示すようにグローバルAJAXエラーハンドラを追加します

  $( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
      if ( jqxhr.status === 403 ) {
          window.location = "login.jsp";
      } else {
          if(thrownError != null) {
              alert(thrownError);
          } else {
              alert("error");
          }
      }
  });

また、JSPページのAJAX呼び出しから既存のエラーハンドラーを削除します。

        var str = $("#viewForm").serialize();
        $.ajax({
            url: "get_mongoDB_doc_versions.do",
            type: "post",
            data: str,
            cache: false,
            async: false,
            dataType: "json",
            success: function(data) { ... },
//            error: function (jqXHR, textStatus, errorStr) {
//                 if(textStatus != null)
//                     alert(textStatus);
//                 else if(errorStr != null)
//                     alert(errorStr);
//                 else
//                     alert("error");
//            }
        });

他の人の役に立つことを願っています。

Update1 form-login設定にオプション(always-use-default-target = "true")を追加する必要があることがわかりました。これは、AJAXリクエストがログインページにリダイレクトされた後(セッションの期限切れのため)、Springが以前のAJAXリクエストを記憶し、ログイン後にそれにリダイレクトするために必要でした。これにより、返されたJSONがブラウザーページに表示されます。もちろん、私が望むものではありません。

Update2 を使用always-use-default-target="true"する代わりに、requstCacheからのAJAXリクエストをブロックする@RobWinchの例を使用します。これにより、ログイン後に通常のリンクを元のターゲットにリダイレクトできますが、AJAXはログイン後にホームページに移動します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.