Cara mengelola permintaan redirect setelah panggilan jQuery Ajax

1370

Saya menggunakan $.post()untuk memanggil servlet menggunakan Ajax dan kemudian menggunakan fragmen HTML yang dihasilkan untuk mengganti divelemen di halaman saat ini pengguna. Namun, jika sesi habis, server mengirimkan arahan pengalihan untuk mengirim pengguna ke halaman login. Dalam hal ini, jQuery mengganti divelemen dengan konten halaman login, memaksa mata pengguna untuk menyaksikan adegan langka.

Bagaimana saya bisa mengelola arahan redirect dari panggilan Ajax dengan jQuery 1.2.6?

Elliot Vargas
sumber
1
(bukan jawaban seperti itu) - Saya telah melakukan ini di masa lalu dengan mengedit perpustakaan jquery dan menambahkan cek untuk halaman login pada setiap XHR lengkap. Bukan solusi terbaik karena harus dilakukan setiap kali Anda meningkatkan, tetapi itu memecahkan masalah.
Sugendran
1
Lihat pertanyaan terkait: stackoverflow.com/questions/5941933/…
Nutel
The HttpContext.Response.AddHeaderdan cek pada sukses ajaxsetup adalah cara untuk pergi
LCJ
4
Mengapa server tidak dapat mengembalikan 401? Jika demikian, Anda dapat memiliki global $ .ajaxSetup dan menggunakan kode status untuk mengarahkan ulang halaman.
Vishal
1
tautan ini doanduyhai.wordpress.com/2012/04/21/… memberi saya solusi yang tepat
pappu_kutty

Jawaban:

697

Saya membaca pertanyaan ini dan menerapkan pendekatan yang telah dinyatakan mengenai pengaturan respons kode status HTTP ke 278 untuk menghindari browser menangani pengalihan secara transparan. Meskipun ini berhasil, saya sedikit tidak puas karena ini sedikit hack.

Setelah menggali lebih dalam, saya membuang pendekatan ini dan menggunakan JSON . Dalam hal ini, semua respons terhadap permintaan AJAX memiliki kode status 200 dan badan respons berisi objek JSON yang dibangun di server. JavaScript pada klien kemudian dapat menggunakan objek JSON untuk memutuskan apa yang perlu dilakukan.

Saya punya masalah serupa dengan Anda. Saya melakukan permintaan AJAX yang memiliki 2 kemungkinan tanggapan: satu yang mengarahkan browser ke halaman baru dan yang menggantikan formulir HTML yang ada di halaman saat ini dengan yang baru. Kode jQuery untuk melakukan ini terlihat seperti:

$.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);
        }
    }
});

"Data" objek JSON dibangun di server untuk memiliki 2 anggota: data.redirectdan data.form. Saya menemukan pendekatan ini jauh lebih baik.

Steg
sumber
58
Seperti yang dinyatakan dalam solusi di stackoverflow.com/questions/503093/... lebih baik menggunakan window.location.replace (data.redirect); dari window.location.href = data.redirect;
Carles Barrobés
8
Alasan apa pun mengapa tidak akan lebih baik menggunakan kode HTTP pada tindakan. Misalnya kode 307 yang merupakan HTTP Temporary Redirect?
Sergei Golos
15
@Sergei Golos alasannya adalah bahwa jika Anda melakukan pengalihan HTTP, pengalihan sebenarnya tidak pernah datang ke panggilan balik sukses ajax. Peramban memproses pengalihan yang memberikan 200 kode dengan konten tujuan pengalihan.
Miguel Silva
2
akan seperti apa kode server? untuk memiliki nilai pengembalian data.form dan data.redirect. pada dasarnya bagaimana saya menentukan apakah saya meletakkan redirect di dalamnya?
Vnge
6
Jawaban ini akan lebih membantu jika telah menunjukkan bagaimana hal ini dilakukan di server.
Pedro Hoehl Carvalho
243

Saya memecahkan masalah ini dengan:

  1. Menambahkan tajuk khusus ke respons:

    public ActionResult Index(){
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
        }
        return View();
    }
  2. Mengikat fungsi JavaScript ke ajaxSuccessacara dan memeriksa untuk melihat apakah header ada:

    $(document).ajaxSuccess(function(event, request, settings) {
        if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
           window.location = '/';
        }
    });
SuperG
sumber
6
Solusi yang luar biasa. Saya suka ide solusi satu atap. Saya perlu memeriksa status 403, tetapi saya dapat menggunakan ikatan ajaxSuccess di badan untuk ini (yang sebenarnya saya cari.) Terima kasih.
Bretticus
4
Saya hanya melakukan ini dan menemukan bahwa saya membutuhkan ajaxComplete di mana saya menggunakan fungsi $ .get () dan status apa pun selain 200 tidak diaktifkan. Bahkan, saya mungkin hanya mungkin terikat pada ajaxError saja. Lihat jawaban saya di bawah ini untuk lebih jelasnya.
Bretticus
2
Tidak masalah dalam banyak kasus, tetapi bagaimana jika kerangka kerja Anda menangani otorisasi?
jwaliszko
13
Saya suka pendekatan header tetapi juga berpikir - seperti @ mwoods79 - bahwa pengetahuan ke mana harus mengarahkan tidak boleh diduplikasi. Saya memecahkannya dengan menambahkan header REDIRECT_LOCATION bukan boolean.
rintcius
3
Berhati-hatilah untuk mengatur tajuk pada respons SETELAH pengalihan. Seperti dijelaskan dalam jawaban lain di halaman ini, pengalihan mungkin transparan ke penangan ajaxSucces. Jadi saya memasukkan header pada respon GET dari halaman login (yang akhirnya dan hanya memicu ajaxSuccess dalam skenario saya).
sieppl
118

Tidak ada browser yang menangani respons 301 dan 302 dengan benar. Dan sebenarnya standar bahkan mengatakan mereka harus menanganinya "secara transparan" yang merupakan sakit kepala BESAR untuk vendor Perpustakaan Ajax. Di Ra-Ajax kami terpaksa menggunakan kode status respons HTTP 278 (hanya beberapa kode sukses "tidak terpakai") untuk menangani pengalihan secara transparan dari server ...

Ini benar-benar mengganggu saya, dan jika seseorang di sini memiliki "tarik" di W3C, saya sangat menghargai bahwa Anda dapat memberi tahu W3C bahwa kami benar-benar perlu menangani 301 dan 302 kode sendiri ...! ;)

Thomas Hansen
sumber
3
Saya satu langkah bahwa 278 harus menjadi terpisah dari Spec HTTP resmi.
Chris Marisic
2
Bukankah itu sudah menangani mereka secara transparan? Jika sumber daya telah dipindahkan, menanganinya secara transparan berarti mengulangi permintaan pada URL yang disediakan. Itulah yang saya harapkan menggunakan API XMLHttpRequest.
Philippe Rathé
@ PhilippeRathé Setuju. Penanganan transparan adalah apa yang saya inginkan. Dan saya tidak tahu mengapa itu dianggap buruk.
smwikipedia
@smwikipedia Untuk mengatur pengalihan markup di bagian utama tanpa mengarahkan ulang halaman.
cmc
jika seseorang di sini memiliki "tarik" di W3C Saya sangat menghargai Anda dapat memberi tahu W3C bahwa kami benar-benar perlu menangani kode 301 dan 302 sendiri ...! ;)
Tommy.Tang
100

Solusi yang akhirnya diterapkan adalah menggunakan pembungkus untuk fungsi panggilan balik panggilan Ajax dan dalam pembungkus ini memeriksa keberadaan elemen tertentu pada potongan HTML yang dikembalikan. Jika elemen ditemukan maka pembungkus melakukan pengalihan. Jika tidak, pembungkus meneruskan panggilan ke fungsi panggilan balik yang sebenarnya.

Misalnya, fungsi pembungkus kami adalah seperti:

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

Kemudian, ketika melakukan panggilan Ajax kami menggunakan sesuatu seperti:

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

Ini berhasil bagi kami karena semua panggilan Ajax selalu mengembalikan HTML di dalam elemen DIV yang kami gunakan untuk mengganti bagian halaman. Selain itu, kami hanya perlu mengarahkan ulang ke halaman login.

Elliot Vargas
sumber
4
Perhatikan bahwa ini dapat disingkat menjadi fungsi cbWrapper (funct) {fungsi pengembalian (data) {if ($ ("# myForm", data) .size ()> 0) top.location.href = "login"; selain itu berfungsi (data); }}. Maka Anda hanya perlu cbWrapper (myActualCB) saat memanggil .post. Ya, kode dalam komentar berantakan tetapi harus dicatat :)
Simen Echholt
Ukuran disusutkan sehingga Anda dapat menggunakan. Panjang di sini di tempat ukuran
sunil
66

Saya suka metode Timmerz dengan sedikit perasan lemon. Jika Anda pernah mendapatkan kembali contentType dari text / html ketika Anda mengharapkan JSON , Anda kemungkinan besar sedang diarahkan. Dalam kasus saya, saya cukup memuat ulang halaman, dan akan diarahkan ke halaman login. Oh, dan periksa bahwa status jqXHR adalah 200, yang tampaknya konyol, karena Anda berada dalam fungsi kesalahan, bukan? Kalau tidak, kasus kesalahan yang sah akan memaksa ulang berulang (oops)

$.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();
    }

});
BrianY
sumber
1
terima kasih banyak Brian, jawaban Anda adalah yang terbaik untuk skenario saya, meskipun saya ingin jika ada pemeriksaan yang lebih aman seperti membandingkan url / halaman mana yang diarahkan ke alih-alih cek "tipe-konten" yang sederhana. Saya tidak dapat menemukan halaman yang diarahkan ke dari objek jqXHR.
Johnny
Saya memeriksa status 401 dan kemudian mengarahkan ulang. Bekerja seperti jagoan.
Eric
55

Gunakan $.ajax()panggilan tingkat rendah :

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

Coba ini untuk pengalihan:

if (xmlHttp.code != 200) {
  top.location.href = '/some/other/page';
}
Sampai
sumber
Saya mencoba untuk menghindari hal-hal tingkat rendah. Ngomong-ngomong, anggap saya menggunakan sesuatu seperti yang Anda gambarkan, bagaimana cara memaksa pengalihan browser begitu saya mendeteksi bahwa kode HTTP 3xx? Tujuan saya adalah mengarahkan ulang pengguna, bukan hanya mengumumkan bahwa sesi mereka telah kedaluwarsa.
Elliot Vargas
4
Btw, $ .ajax () bukan level yang sangat, sangat rendah. Ini hanya level rendah dalam hal jQuery karena ada $ .get, $ .post, dll. Yang jauh lebih sederhana daripada $ .ajax dan semua opsinya.
Hingga
16
Oh Boy! Maaf saya harus "tidak menerima" jawaban Anda, itu masih sangat membantu. Masalahnya, pengalihan secara otomatis dikelola oleh XMLHttpRequest, maka saya SELALU mendapatkan kode status 200 setelah pengalihan (sigh!). Saya pikir saya harus melakukan sesuatu yang jahat seperti mem-parsing HTML dan mencari marker.
Elliot Vargas
1
Hanya ingin tahu, jika sesi berakhir di server, bukankah itu berarti server mengirim SESSIONID yang berbeda? tidak bisakah kita mendeteksi itu?
Salamander2007
10
CATATAN: Ini tidak berfungsi untuk pengalihan. ajax akan pergi ke halaman baru dan mengembalikan kode statusnya.
33

Saya hanya ingin membagikan pendekatan saya karena ini mungkin dapat membantu seseorang:

Saya pada dasarnya termasuk modul JavaScript yang menangani hal-hal otentikasi seperti menampilkan nama pengguna dan juga kasus ini menangani pengalihan ke halaman login .

Skenario saya: Kami pada dasarnya memiliki server ISA di antara yang mendengarkan semua permintaan dan merespons dengan header 302 dan lokasi ke halaman login kami.

Dalam modul JavaScript saya, pendekatan awal saya adalah sesuatu seperti

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

Masalahnya (seperti banyak di sini sudah disebutkan) adalah bahwa browser menangani pengalihan dengan sendirinya oleh karena itu ajaxCompletepanggilan balik saya tidak pernah dipanggil, tetapi sebaliknya saya mendapat respon dari halaman Login yang sudah dialihkan yang jelas adalah a status 200. Masalahnya: bagaimana Anda mendeteksi apakah respons 200 yang berhasil adalah halaman login Anda yang sebenarnya atau hanya halaman sewenang-wenang lainnya ??

Solusinya

Karena saya tidak dapat menangkap 302 redirect response, saya menambahkan LoginPageheader pada halaman login saya yang berisi url halaman login itu sendiri. Dalam modul saya sekarang mendengarkan header dan melakukan redirect:

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

... dan itu berfungsi seperti pesona :). Anda mungkin bertanya-tanya mengapa saya memasukkan url di LoginPageheader ... yah pada dasarnya karena saya tidak menemukan cara untuk menentukan url yang GETdihasilkan dari pengalihan lokasi otomatis dari xhrobjek ...

Juri
sumber
1 - tapi header kustom seharusnya untuk memulai dengan X-, sehingga header yang lebih baik untuk digunakan akan X-LoginPage: http://example.com/login.
uınb12s
6
@ShaquinTrifonoff Tidak lagi. Saya tidak menggunakan awalan X-karena pada Juni 2011 dokumen ITEF mengusulkan penghentian mereka dan memang, dengan Juni 2012 itu tidak resmi bahwa header kustom tidak boleh lagi diawali dengan X-.
Juri
Kami juga memiliki server ISA dan saya hanya mengalami masalah yang sama. Daripada mengerjakannya dalam kode, kami menggunakan instruksi di kb2596444 untuk mengkonfigurasi ISA untuk berhenti mengarahkan ulang.
scott stone
29

Saya tahu topik ini sudah lama, tetapi saya akan memberikan pendekatan lain yang saya temukan dan sebelumnya dijelaskan di sini . Pada dasarnya saya menggunakan ASP.MVC dengan WIF (tapi ini tidak terlalu penting untuk konteks topik ini - jawabannya memadai tidak peduli kerangka mana yang digunakan. Petunjuk tetap tidak berubah - berurusan dengan masalah yang berkaitan dengan kegagalan otentikasi saat melakukan permintaan ajax ) .

Pendekatan yang ditunjukkan di bawah ini dapat diterapkan untuk semua permintaan ajax di luar kotak (jika mereka tidak mendefinisikan kembali sebelum acara Kirim jelas).

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

Sebelum permintaan ajax dijalankan, CheckPulsemetode dipanggil (metode pengontrol yang bisa menjadi sesuatu yang paling sederhana):

[Authorize]
public virtual void CheckPulse() {}

Jika pengguna tidak diautentikasi (token telah kedaluwarsa) metode tersebut tidak dapat diakses (dilindungi oleh Authorizeatribut). Karena kerangka kerja menangani otentikasi, sementara token kedaluwarsa, ia menempatkan http status 302 ke respons. Jika Anda tidak ingin browser Anda menangani 302 respons secara transparan, tangkap di Global.asax dan ubah status respons - misalnya menjadi 200 OK. Selain itu, tambahkan tajuk, yang memerintahkan Anda untuk memproses respons tersebut dengan cara khusus (nanti di sisi klien):

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

Akhirnya di sisi klien periksa tajuk khusus semacam itu. Jika ada - pengalihan penuh ke halaman masuk harus dilakukan (dalam kasus saya window.locationdiganti dengan url dari permintaan yang ditangani secara otomatis oleh kerangka kerja saya).

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;
                }
            }
    });
}
jwaliszko
sumber
Saya memperbaiki masalah dengan menggunakan acara PostAuthenticateRequest alih-alih acara EndRequest.
Frinavale
@JaroslawWaliszko Saya menempel peristiwa yang salah ke balasan terakhir saya! Maksud saya acara PreSendRequestHeaders ..... bukan PostAuthenticateRequest! >> memerah << terima kasih telah menunjukkan kesalahan saya.
Frinavale
@JaroslawWaliszko saat menggunakan WIF Anda juga dapat mengembalikan 401 respons untuk permintaan AJAX dan biarkan javascript Anda menangani ini. Juga Anda mengasumsikan bahwa semua 302 memerlukan otentikasi yang mungkin tidak benar dalam semua kasus. Saya telah menambahkan jawaban jika ada yang tertarik.
Rob
26

Saya pikir cara yang lebih baik untuk menangani ini adalah dengan memanfaatkan kode respons protokol HTTP yang ada, khususnya 401 Unauthorized.

Inilah cara saya menyelesaikannya:

  1. Sisi server: Jika sesi berakhir, dan permintaan ajax. kirim tajuk kode respons 401
  2. Sisi klien: Ikat ke acara 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 ini lebih umum dan Anda tidak menulis beberapa custom spec / header. Anda juga tidak harus mengubah panggilan ajax yang ada.

Sunting: Komentar Per @ Rob di bawah ini, 401 (kode status HTTP untuk kesalahan otentikasi) harus menjadi indikator. Lihat 403 Forbidden vs 401 Respons HTTP tidak sah untuk detail lebih lanjut. Dengan itu dikatakan beberapa kerangka kerja web menggunakan 403 untuk kedua kesalahan otentikasi DAN otorisasi - jadi beradaptasi sesuai. Rob terima kasih.

rynop
sumber
Saya menggunakan pendekatan yang sama. Apakah jQuery benar-benar memanggil ajaxSuccess pada kode kesalahan 403? Saya pikir hanya bagian ajaxError yang benar-benar diperlukan
Marius Balčytis
24

Saya menyelesaikan masalah ini seperti ini:

Tambahkan middleware untuk memproses respons, jika itu merupakan redirect untuk permintaan ajax, ubah respons menjadi respons normal dengan url redirect.

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

Kemudian di ajaxComplete, jika responsnya berisi redirect, itu harus redirect, jadi ubah lokasi browser.

$('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;
       }
   }
}
Tyr
sumber
20

Saya punya solusi sederhana yang berfungsi untuk saya, tidak diperlukan perubahan kode server ... cukup tambahkan satu sendok teh pala ...

$(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 );
        };
    });
});

Saya memeriksa keberadaan tag html, tetapi Anda dapat mengubah indexOf untuk mencari string unik apa pun yang ada di halaman login Anda ...

Timmerz
sumber
Ini sepertinya tidak berfungsi untuk saya, ia terus memanggil fungsi yang didefinisikan dengan panggilan ajax, itu seperti itu tidak mengesampingkan metode sukses.
adriaanp
Sepertinya tidak bekerja untuk saya, setidaknya lagi. Penjelasan yang mungkin di sini: stackoverflow.com/a/12010724/260665
Raj Pawan Gumdal
20

Solusi lain yang saya temukan (terutama berguna jika Anda ingin mengatur perilaku global) adalah menggunakan $.ajaxsetup()metode ini bersama-sama dengan statusCodeproperti . Seperti yang ditunjukkan orang lain, jangan gunakan redirect statuscode ( 3xx), alih-alih gunakan 4xxkode status dan tangani redirect sisi klien.

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

Ganti 400dengan kode status yang ingin Anda tangani. Seperti yang sudah disebutkan 401 Unauthorizedbisa menjadi ide bagus. Saya menggunakan 400karena ini sangat tidak spesifik dan saya dapat menggunakan 401untuk kasus yang lebih spesifik (seperti kredensial login yang salah). Jadi, alih-alih mengarahkan ulang secara langsung, backend Anda harus mengembalikan 4xxkode kesalahan ketika sesi habis dan Anda menangani sisi klien redirect. Bekerja sempurna untuk saya bahkan dengan kerangka kerja seperti backbone.js

morten.c
sumber
Di mana menyebutkan fungsi pada halaman?
Vikrant
@ Vikrant Jika saya memahami pertanyaan Anda dengan benar, Anda dapat memanggil fungsi tepat setelah jQuery dimuat dan sebelum Anda melakukan permintaan Anda yang sebenarnya.
morten.c
19

Sebagian besar solusi yang diberikan menggunakan solusi, menggunakan header tambahan atau kode HTTP yang tidak tepat. Solusi-solusi itu kemungkinan besar akan bekerja tetapi terasa sedikit 'berantakan'. Saya telah menemukan solusi lain.

Kami menggunakan WIF yang dikonfigurasi untuk mengarahkan ulang (passiveRedirectEnabled = "true") pada respons 401. Redirect berguna ketika menangani permintaan normal tetapi tidak akan berfungsi untuk permintaan AJAX (karena browser tidak akan menjalankan 302 / redirect).

Menggunakan kode berikut di global.asax Anda, Anda dapat menonaktifkan redirect untuk permintaan 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;
        }
    }

Ini memungkinkan Anda mengembalikan 401 respons untuk permintaan AJAX, yang kemudian dapat ditangani oleh javascript Anda dengan memuat ulang halaman. Reload halaman akan melemparkan 401 yang akan ditangani oleh WIF (dan WIF akan mengarahkan pengguna ke halaman login).

Contoh javascript untuk menangani 401 kesalahan:

$(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);
    }
});
rampok
sumber
Solusi yang bagus Terima kasih.
DanMan
19

Masalah ini dapat muncul kemudian menggunakan metode ASP.NET MVC RedirectToAction. Untuk mencegah formulir menampilkan respons dalam div, Anda cukup melakukan semacam filter respons ajax untuk respons incomming dengan $ .ajaxSetup . Jika responsnya berisi pengalihan MVC Anda dapat mengevaluasi ekspresi ini di sisi JS. Contoh kode untuk JS di bawah ini:

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

Jika datanya adalah: "window.location = '/ Acount / Login'" filter di atas akan menangkapnya dan mengevaluasi untuk membuat redirection alih-alih membiarkan data ditampilkan.

Przemek Marcinkiewicz
sumber
dataada di badan respons atau tajuk?
GMsoF
16

Menyatukan apa yang dikatakan Vladimir Prudnikov dan Thomas Hansen:

  • Ubah kode sisi server Anda untuk mendeteksi apakah itu XHR. Jika ya, setel kode respons pengalihan ke 278. Dalam Djakarta:
   if request.is_ajax():
      response.status_code = 278

Ini membuat browser memperlakukan respons sebagai sukses, dan menyerahkannya ke Javascript Anda.

  • Di JS Anda, pastikan pengiriman formulir melalui Ajax, periksa kode respons dan arahkan jika diperlukan:
$('#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); 
});
Graham King
sumber
13
    <script>
    function showValues() {
        var str = $("form").serialize();
        $.post('loginUser.html', 
        str,
        function(responseText, responseStatus, responseXML){
            if(responseStatus=="success"){
                window.location= "adminIndex.html";
            }
        });     
    }
</script>
Priyanka
sumber
12

Mencoba

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

Letakkan di halaman login. Jika itu dimuat dalam div di halaman utama, itu akan mengarahkan ulang sampai halaman login. "#site" adalah id dari div yang terletak di semua halaman kecuali halaman login.

podeig
sumber
12

Sementara jawaban tampaknya bekerja untuk orang-orang jika Anda menggunakan Spring Security, saya telah menemukan memperluas LoginUrlAuthenticationEntryPoint dan menambahkan kode khusus untuk menangani AJAX lebih kuat. Sebagian besar contoh mencegat semua pengalihan bukan hanya kegagalan otentikasi. Ini tidak diinginkan untuk proyek yang saya kerjakan. Anda mungkin menemukan perlu untuk memperpanjang ExceptionTranslationFilter dan mengganti metode "sendStartAuthentication" untuk menghapus langkah caching jika Anda tidak ingin cache AJAX yang gagal di-cache.

Contoh 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"));
    }
}

Sumber: 1 , 2

John
sumber
3
Akan sangat membantu (bagi saya) jika pemilih bawah akan menjelaskan mengapa mereka memilih. Jika ada sesuatu yang buruk dengan solusi ini saya ingin belajar dari kesalahan saya. Terima kasih.
John
Jika menggunakan Spring dan juga menggunakan JSF, maka periksa juga untuk ini: ("partial / ajax"). EqualsIgnoreCase (request.getHeader ("wajah-permintaan"));
J Slick
Pengguna mungkin menolak karena Anda tidak menyebutkan: (1) mod sisi klien yang diperlukan untuk mendeteksi respons kesalahan Anda; (2) mod yang diperlukan untuk konfigurasi Spring untuk menambahkan LoginUrlAuthenticationEntryPoint Anda yang disesuaikan ke rantai filter.
J Slick
Jawaban Anda mirip dengan yang ini dari @Arpad. Itu bekerja untuk saya; menggunakan Spring Security 3.2.9. stackoverflow.com/a/8426947/4505142
Darren Parker
11

Saya memecahkan masalah ini dengan meletakkan yang berikut di halaman login.php saya.

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

Biarkan saya kutip lagi masalah seperti yang dijelaskan oleh @Steg

Saya punya masalah serupa dengan Anda. Saya melakukan permintaan ajax yang memiliki 2 kemungkinan respons: satu yang mengarahkan browser ke halaman baru dan yang menggantikan formulir HTML yang ada di halaman saat ini dengan yang baru.

IMHO ini adalah tantangan nyata dan harus secara resmi diperluas ke standar HTTP saat ini.

Saya percaya Standar Http baru akan menggunakan kode status baru. artinya: saat ini 301/302memberitahu browser untuk pergi dan mengambil konten dari permintaan ini ke yang barulocation .

Dalam standar yang diperluas, itu akan mengatakan bahwa jika respon status: 308(hanya sebuah contoh), maka browser harus mengarahkan halaman utama kelocation disediakan.

Yang telah dibilang; Saya cenderung sudah meniru perilaku masa depan ini , dan karena itu ketika document.redirect diperlukan, saya memiliki server merespons sebagai:

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

Ketika JS mendapat " status: 204", ia memeriksa keberadaan x-status: 308header, dan melakukan document.redirect ke halaman yang disediakan dilocation header.

Apakah ini masuk akal bagi Anda?

Chaim Klar
sumber
7

Beberapa mungkin menemukan hal di bawah ini berguna:

Saya ingin klien dialihkan ke halaman login untuk tindakan pemulihan apa pun yang dikirim tanpa token otorisasi. Karena semua tindakan istirahat saya berbasis Ajax, saya membutuhkan cara umum yang baik untuk mengarahkan ulang ke halaman login daripada menangani fungsi sukses Ajax.

Inilah yang telah saya lakukan:

Pada permintaan Ajax apa pun server saya akan mengembalikan respons Json 200 "PERLU OTENTIK" (jika klien perlu mengautentikasi).

Contoh sederhana di Java (sisi server):

@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());
        }
    }
}

Di Javascript saya, saya telah menambahkan kode berikut:

$.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);
        }
    };      
});

Dan itu saja.

Tomer
sumber
5

di servlet Anda harus meletakkan response.setStatus(response.SC_MOVED_PERMANENTLY); mengirim status xmlHttp '301' yang Anda perlukan untuk pengalihan ...

dan dalam fungsi $ .ajax Anda seharusnya tidak menggunakan .toString()fungsi ..., adil

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

masalahnya adalah itu tidak terlalu fleksibel, Anda tidak dapat memutuskan di mana Anda ingin mengarahkan ..

mengarahkan melalui servlets harus menjadi cara terbaik. tetapi saya masih belum dapat menemukan cara yang tepat untuk melakukannya.


sumber
5

Saya hanya ingin mengaitkan permintaan ajax untuk seluruh halaman. @ Super membuat saya mulai. Inilah yang akhirnya saya dapatkan:

// 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;
        }
});

Saya ingin secara khusus memeriksa kode status http tertentu untuk mendasarkan keputusan saya. Namun, Anda hanya dapat mengikat ke ajaxError untuk mendapatkan apa pun selain kesuksesan (hanya 200 mungkin?) Saya bisa saja menulis:

$('body').bind('ajaxError', function(event,request,settings){
    window.location.replace("http://mysite.tld/login");
}
Bretticus
sumber
1
yang terakhir akan menyembunyikan kesalahan lain yang membuat pemecahan masalah menjadi bermasalah
Tim Abell
1
A 403 tidak berarti bahwa pengguna tidak diautentikasi, itu berarti bahwa pengguna (mungkin diautentikasi) tidak memiliki izin untuk melihat sumber daya yang diminta. Jadi seharusnya tidak diarahkan ke halaman login
Rob
5

Jika Anda juga ingin meneruskan nilai-nilai maka Anda juga dapat mengatur variabel sesi dan mengakses Mis: Di jsp Anda, Anda dapat menulis

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

Dan kemudian Anda dapat menyimpan nilai temp ini dalam variabel javascript Anda dan bermain-main

karthik339
sumber
5

Saya tidak berhasil dengan solusi header - mereka tidak pernah diambil dalam metode ajaxSuccess / ajaxComplete saya. Saya menggunakan jawaban Steg dengan respons khusus, tetapi saya memodifikasi beberapa sisi JS. Saya mengatur metode yang saya panggil di setiap fungsi sehingga saya dapat menggunakan metode $.getdan standar $.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);
    }
}

Contoh itu digunakan ...

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;
}
jocull
sumber
5

Akhirnya, saya memecahkan masalah dengan menambahkan kebiasaan HTTP Header . Tepat sebelum respons untuk setiap permintaan di sisi server, saya menambahkan url yang diminta saat ini ke header respons.

Jenis aplikasi saya di server adalah Asp.Net MVC, dan memiliki tempat yang baik untuk melakukannya. di Global.asaxi mengimplementasikan Application_EndRequestacara jadi:

    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);
        }

    }

Ini berfungsi sempurna untuk saya! Sekarang di setiap respons dari JQuery $.postsaya memiliki urlheader respons yang diminta dan juga lainnya yang datang sebagai hasil dari POSTmetode dengan status 302,303 , ....

dan hal penting lainnya adalah tidak perlu memodifikasi kode di sisi server atau sisi klien.

dan selanjutnya adalah kemampuan untuk mendapatkan akses ke informasi lain dari tindakan posting seperti kesalahan, pesan, dan ..., Dengan cara ini.

Saya memposting ini, mungkin membantu seseorang :)

Ali Adlavaran
sumber
4

Saya mengalami masalah ini pada aplikasi Django yang saya mainkan (disclaimer: Saya bermain-main untuk belajar, dan saya sama sekali tidak ahli). Yang ingin saya lakukan adalah menggunakan jQuery ajax untuk mengirim permintaan DELETE ke sumber daya, menghapusnya di sisi server, kemudian mengirim redirect kembali ke (pada dasarnya) beranda. Ketika saya mengirim HttpResponseRedirect('/the-redirect/')dari skrip python, metode ajax jQuery menerima 200 bukannya 302. Jadi, yang saya lakukan adalah mengirim respons 300 dengan:

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

Kemudian saya mengirim / menangani permintaan pada klien dengan jQuery.ajax seperti:

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

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

Mungkin menggunakan 300 bukan "benar", tetapi setidaknya itu berfungsi seperti yang saya inginkan.

PS: ini sangat menyusahkan untuk diedit pada SO versi mobile. ISP bodoh memasukkan permintaan pembatalan layanan saya tepat ketika saya selesai dengan jawaban saya!

Benny Jobigan
sumber
4

Anda juga dapat mengaitkan XMLHttpRequest mengirim prototipe. Ini akan bekerja untuk semua pengiriman (jQuery / dojo / etc) dengan satu penangan.

Saya menulis kode ini untuk menangani kesalahan 500 halaman yang kadaluwarsa, tetapi harus berfungsi juga untuk menjebak 200 redirect. Siapkan entri wikipedia di XMLHttpRequest onreadystatechange tentang arti readyState.

// 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);
}
Curtis Yallop
sumber
2

Selain itu Anda mungkin ingin mengarahkan pengguna ke URL header yang diberikan di. Jadi akhirnya akan terlihat seperti ini:

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

UPD: Opps. Punya tugas yang sama, tetapi tidak berhasil. Melakukan hal ini. Saya akan menunjukkan solusi kepada Anda ketika saya akan menemukannya.

Vladimir Prudnikov
sumber
4
Jawaban ini harus dihapus oleh penulis karena ia sendiri mengatakan bahwa ini tidak berfungsi. Dia dapat memposting solusi kerja nanti. -1
TheCrazyProgrammer
Idenya bagus, tetapi masalahnya adalah tajuk "Lokasi" tidak pernah dilewati.
Martin Zvarík
Hasil edit saya ditolak, jadi ... Anda harus menggunakan "var xmlHttp = $ .ajax ({...." variabel tidak dapat berada di dalam ... lalu gunakan: console.log (xmlHttp.getAllResponseHeaders ()) ;
Martin Zvarík
2

Saya mendapat solulion yang berfungsi menggunakan jawaban dari tautan @John dan @Arpad dan tautan @RobWinch

Saya menggunakan Spring Security 3.2.9 dan jQuery 1.10.2.

Perpanjang kelas Spring menyebabkan respons 4XX hanya dari permintaan AJAX:

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>

Di JSP saya, tambahkan penangan kesalahan global AJAX seperti yang ditunjukkan di sini

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

Juga, hapus penangan kesalahan yang ada dari panggilan AJAX di halaman JSP:

        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");
//            }
        });

Saya harap ini membantu orang lain.

Update1 Saya menemukan bahwa saya perlu menambahkan opsi (selalu-gunakan-default-target = "true") ke konfigurasi form-login. Ini diperlukan karena setelah permintaan AJAX dialihkan ke halaman login (karena sesi kedaluwarsa), Spring mengingat permintaan AJAX sebelumnya dan auto redirect ke setelah login. Ini menyebabkan JSON yang dikembalikan ditampilkan pada halaman browser. Tentu saja, bukan yang saya inginkan.

Update2 Alih-alih menggunakan always-use-default-target="true", gunakan contoh @RobWinch untuk memblokir permintaan AJAX dari requstCache. Ini memungkinkan tautan normal untuk diarahkan ke target semula setelah login, tetapi AJAX pergi ke halaman utama setelah login.

Darren Parker
sumber