2010-07-26 4 views
22

Używam spring-security i jQuery w mojej aplikacji. Strona główna dynamicznie ładuje zawartość do kart przez Ajax. Wszystko jest w porządku, ale czasami mam stronę logowania w mojej zakładce, a jeśli wpiszę poświadczenia, zostanie przekierowany do strony zawartości bez kart.Jak obsłużyć wygasłą sesję przy użyciu funkcji spring-security i jQuery?

Chciałbym poradzić sobie z tą sytuacją. Wiem, że niektórzy ludzie używają uwierzytelniania ajaxowego, ale nie jestem pewien, czy jest odpowiedni dla mnie, ponieważ wydaje mi się to dość skomplikowane, a moja aplikacja nie pozwala na żaden dostęp bez wcześniejszego logowania. Chciałbym napisać globalny handler dla wszystkich odpowiedzi ajax, które wykonają window.location.reload(), jeśli potrzebujemy uwierzytelnienia. Myślę, że w tym przypadku lepiej jest uzyskać błąd 401 zamiast standardowego formularza logowania, ponieważ jest on łatwiejszy w obsłudze.

Więc

1) Czy jest możliwe aby napisać globalnej obsługi błędów dla wszystkich żądań AJAX jQuery?

2) Jak mogę dostosować zachowanie zabezpieczeń sprężynowych, aby wysyłać błąd 401 dla żądań ajax, ale dla zwykłych żądań, aby pokazać standardową stronę logowania jak zwykle?

3) Może masz więcej wdzięcznego rozwiązania? Udostępnij to.

Dzięki.

+0

Minęło trochę czasu, odkąd o to poprosiłeś. Czy sam wymyśliłeś dobre rozwiązanie? –

+1

Niedawno napisałem wpis na blogu na ten temat: http://www.to-string.com/2012/08/03/springsecurity-authenticating-authorizing-ajax-requests/ – craftsman

+0

Podoba mi się rozwiązanie dla robotników. Uprośniłem to (przynajmniej tak myślę). Zobacz http://gedrox.blogspot.com/2013/03/blog-post.html. – Gedrox

Odpowiedz

2

Oto, jak zwykle to robię. Przy każdym wywołaniu AJAX sprawdź wynik przed jego użyciem.

$.ajax({ type: 'GET', 
    url: GetRootUrl() + '/services/dosomething.ashx', 
    success: function (data) { 
     if (HasErrors(data)) return; 

     // process data returned... 

    }, 
    error: function (xmlHttpRequest, textStatus) { 
     ShowStatusFailed(xmlHttpRequest); 
    } 
    }); 

I wtedy funkcja HasErrors() wygląda tak, a może być dzielona na wszystkich stronach.

function HasErrors(data) { 
    // check for redirect to login page 
    if (data.search(/login\.aspx/i) != -1) { 
    top.location.href = GetRootUrl() + '/login.aspx?lo=TimedOut'; 
    return true; 
    } 
    // check for IIS error page 
    if (data.search(/Internal Server Error/) != -1) { 
    ShowStatusFailed('Server Error.'); 
    return true; 
    } 
    // check for our custom error handling page 
    if (data.search(/Error.aspx/) != -1) { 
    ShowStatusFailed('An error occurred on the server. The Technical Support Team has been provided with the error details.'); 
    return true; 
    } 
    return false; 
} 
3

Po prostu znalazłem rozwiązanie tego problemu, ale nie przetestowałem go dokładnie. Używam również sprężyny, bezpieczeństwa sprężynowego i jQuery. Po pierwsze, z kontrolerem mój login użytkownika, ustawić kod stanu do 401:

LoginController { 

public ModelAndView loginHandler(HttpServletRequest request, HttpServletResponse response) { 

... 
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
... 
return new ModelAndView("login", model); 
} 

W ich onload() metody, wszystkich moich stron wywołać funkcję w moim globalnej javascript pliku:

function initAjaxErrors() { 

jQuery(window).ajaxError(function(event, xmlHttpRequest, ajaxOptions, thrownError) { 
    if (403 == xmlHttpRequest.status) 
     showMessage("Permission Denied"); 
    else 
     showMessage("An error occurred: "+xmlHttpRequest.status+" "+xmlHttpRequest.statusText); 
}); 

}

W tym momencie możesz obsłużyć błąd 401 w dowolny sposób. W jednym projekcie zajmuję się uwierzytelnianiem jQuery przez umieszczenie okna dialogowego jQuery wokół elementu iframe zawierającego formularz logowania.

4

Spójrz na http://forum.springsource.org/showthread.php?t=95881, myślę, że proponowane rozwiązanie jest znacznie jaśniejsze niż innych odpowiedzi tutaj:

  1. Dodaj niestandardowy nagłówek w jquery ajax połączeń (za pomocą haka „beforeSend”). Możesz także użyć nagłówka "X-Requested-With", który wysyła jQuery.
  2. Skonfiguruj Spring Security, aby wyszukać ten nagłówek po stronie serwera, aby zwrócić kod błędu HTTP 401 zamiast zabrać użytkownika do strony logowania.
+1

A jeśli ktoś użyje '$ .getJSON() {...}'? "beforeSend" wtedy nie jest możliwe. – zygimantus

10

Oto podejście, które moim zdaniem jest dość proste. Jest to połączenie podejść, które zaobserwowałem na tej stronie.Pisałem na blogu o tym: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/

Podstawową ideą jest wykorzystanie API url prefiks (tj/api/zabezpieczone), jak zasugerowano powyżej wraz z punktu wejścia uwierzytelniania. To proste i działa.

Oto punkt wejścia uwierzytelniania:

package com.yoyar.yaya.config; 

import org.springframework.security.core.AuthenticationException; 
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; 

import javax.servlet.ServletException; 
import javax.servlet.http.*; 
import java.io.IOException; 

public class AjaxAwareAuthenticationEntryPoint 
      extends LoginUrlAuthenticationEntryPoint { 

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

    @Override 
    public void commence(
     HttpServletRequest request, 
     HttpServletResponse response, 
     AuthenticationException authException) 
      throws IOException, ServletException { 

     boolean isAjax 
      = request.getRequestURI().startsWith("/api/secured"); 

     if (isAjax) { 
      response.sendError(403, "Forbidden"); 
     } else { 
      super.commence(request, response, authException); 
     } 
    } 
} 

A oto, co się dzieje w twojej wiosennej kontekstowego xml:

<bean id="authenticationEntryPoint" 
    class="com.yoyar.yaya.config.AjaxAwareAuthenticationEntryPoint"> 
    <constructor-arg name="loginUrl" value="/login"/> 
</bean> 

<security:http auto-config="true" 
    use-expressions="true" 
    entry-point-ref="authenticationEntryPoint"> 
    <security:intercept-url pattern="/api/secured/**" access="hasRole('ROLE_USER')"/> 
    <security:intercept-url pattern="/login" access="permitAll"/> 
    <security:intercept-url pattern="/logout" access="permitAll"/> 
    <security:intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/> 
    <security:intercept-url pattern="/" access="permitAll"/> 
    <security:form-login login-page="/login" 
         authentication-failure-url="/loginfailed" 
         default-target-url="/login/success"/> 
    <security:access-denied-handler error-page="/denied"/> 
    <security:logout invalidate-session="true" 
        logout-success-url="/logout/success" 
        logout-url="/logout"/> 
</security:http> 
+0

Zaimplementowałem to podejście i łapię limit czasu, a następnie przekierowuję do strony logowania, aby zalogować się ponownie. Po zalogowaniu widzę URL ajax i ciąg zapytania. Czy istnieje sposób, w jaki mogę wrócić do strony, z której inicjowane jest żądanie ajax? Dzięki – blong824

+0

To świetny punkt.Jest to cecha bezpieczeństwa wiosennego i ortogonalna dla tego rozwiązania. Jest to jednak problem. Obecnie mam ten sam problem i zaktualizuję ten wpis, gdy go usuniemy. Daj mi znać, jeśli rozwiążesz ten problem w międzyczasie. –

+0

Re: komentarz blong824, ta strona jest pouczająca: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/appendix-namespace.html#nsa-form-login. Jeśli ustawisz parametr "always-use-default-target" na true, system może zawsze przekierowywać po zalogowaniu na żądaną stronę. Poszukaj także rozwiązań związanych z typem komponentu bean: 'SimpleUrlAuthenticationSuccessHandler'. Myślę, że bardziej złożone rozwiązania najlepiej opisać w osobnym poście. –

8

użyłem następujące rozwiązanie.

W bezpieczeństwa sprężyny określono nieprawidłowy sesji url

<security:session-management invalid-session-url="/invalidate.do"/> 

Na tej stronie dodane po regulatorowi

@Controller 
public class InvalidateSession 
{ 
    /** 
    * This url gets invoked when spring security invalidates session (ie timeout). 
    * Specific content indicates ui layer that session has been invalidated and page should be redirected to logout. 
    */ 
    @RequestMapping(value = "invalidate.do", method = RequestMethod.GET) 
    @ResponseBody 
    public String invalidateSession() { 
     return "invalidSession"; 
    } 
} 

I AJAX używane ajaxSetup obsłużyć wszystkie ajax żądania:

// Checks, if data indicates that session has been invalidated. 
// If session is invalidated, page is redirected to logout 
    $.ajaxSetup({ 
    complete: function(xhr, status) { 
       if (xhr.responseText == 'invalidSession') { 
        if ($("#colorbox").count > 0) { 
         $("#colorbox").destroy(); 
        } 
        window.location = "logout"; 
       } 
      } 
     }); 
+0

W moim przypadku, aby to działało, musiałem dodać 'invalidate-session =" false "' do '', inaczej wiosna przekierowała mnie na '/ invalidate.do' po kliknięciu przycisku wylogowania. – matthaeus

+0

Czy to działa, jeśli używam 'expired-url ="/login "'? – zygimantus

0

Tak tutaj są 2 problemy. 1) Wiosenne bezpieczeństwo działa, ale odpowiedź powraca do przeglądarki w wywołaniu ajax. 2) Wiosenne bezpieczeństwo śledzi pierwotnie żądaną stronę, aby mogła przekierować cię na nią po zalogowaniu (chyba że określisz, że zawsze chcesz używać określonej strony po zalogowaniu się). W tym przypadku żądanie było łańcuchem Ajax, więc zostaniesz przekierowany do tego łańcucha i to właśnie zobaczysz w przeglądarce.

Prostym rozwiązaniem jest wykrycie błędu Ajax, a jeśli żądanie odesłane jest specyficzne dla twojej strony logowania (Spring odeśle stronę HTML logowania, będzie to właściwość "responseText" żądania) wykryj ją . Następnie ponownie załaduj swoją bieżącą stronę, co spowoduje usunięcie użytkownika z kontekstu połączenia Ajax. Spring następnie automatycznie wyśle ​​je na stronę logowania. (Używam domyślnej j_username, która jest wartością ciągową, która jest unikalna dla mojej strony logowania).

$(document).ajaxError(function(event, request, settings, exception) { 
    if(String.prototype.indexOf.call(request.responseText, "j_username") != -1) { 
     window.location.reload(document.URL); 
    } 
}); 
0

przypadku przekroczenia limitu czasu, użytkownik zostaje przekierowany do strony logowania po wyzwoleniu każdy ajax akcja podczas sesji już wyczyszczone

kontekst zabezpieczeń:

<http use-expressions="true" entry-point-ref="authenticationEntryPoint"> 
    <logout invalidate-session="true" success-handler-ref="logoutSuccessBean" delete-cookies="JSESSIONID" /> 
    <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" /> 
    <custom-filter position="FORM_LOGIN_FILTER" ref="authFilter" /> 
    <session-management invalid-session-url="/logout.xhtml" session-authentication-strategy-ref="sas"/> 
</http> 

<beans:bean id="concurrencyFilter" 
    class="org.springframework.security.web.session.ConcurrentSessionFilter"> 
    <beans:property name="sessionRegistry" ref="sessionRegistry" /> 
    <beans:property name="expiredUrl" value="/logout.xhtml" /> 
</beans:bean> 

<beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> 
    <beans:property name="loginFormUrl" value="/login.xhtml" /> 
</beans:bean> 

<beans:bean id="authFilter" 
    class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> 
    <beans:property name="sessionAuthenticationStrategy" ref="sas" /> 
    <beans:property name="authenticationManager" ref="authenticationManager" /> 
    <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessBean" /> 
    <beans:property name="authenticationFailureHandler" ref="authenticationFailureBean" /> 
</beans:bean> 

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> 
    <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> 
    <beans:property name="maximumSessions" value="1" /> 
    <beans:property name="exceptionIfMaximumExceeded" value="1" /> 
</beans:bean> 

Logowanie słuchacza:

public class LoginListener implements PhaseListener { 

@Override 
public PhaseId getPhaseId() { 
    return PhaseId.RESTORE_VIEW; 
} 

@Override 
public void beforePhase(PhaseEvent event) { 
    // do nothing 
} 

@Override 
public void afterPhase(PhaseEvent event) { 
    FacesContext context = event.getFacesContext(); 
    HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); 
    String logoutURL = request.getContextPath() + "/logout.xhtml"; 
    String loginURL = request.getContextPath() + "/login.xhtml"; 

    if (logoutURL.equals(request.getRequestURI())) { 
     try { 
      context.getExternalContext().redirect(loginURL); 
     } catch (IOException e) { 
      throw new FacesException(e); 
     } 
    } 
} 

}

Powiązane problemy