20 Şubat 2013 Çarşamba

Spring Security Invalid Session Ajax Redirect Sorunu

Merhaba,

Bu aralar sıkça uğraştığım Spring Security hakkında yazdığım yazılara devam ediyorum. Bu seferki problem invalid hale gelmiş bir sessionda ajax request gönderdiğimizde view kısmında herhangi bir aksiyon olmaması. Önce problemin kaynağını belirteyim. Giden standart bir request'in response'ı ekrada düzgün bir şekilde gösterilirken giden bir ajax requestin response'ı partial-response olmalıdır. Uygulamada herhangi bir sayfadayken session timeout gerçekleştiğinde ve biz sonrasında ajax request gönderen bir işlem yaptığımızda spring security'nin standart jsf redirection metodu ajax response'ı redirect edemiyor. Bu noktada yapmamız gereken custom bir RedirectStrategy yazmak. Bu sınıf InvalidSessionStrategy sınıfını implement etmeli ve onInvalidSessionDetected metodunda ajax requesti yakalayıp ona göre farklı bir işlem yapmalıyız. İsterseniz burada http error gönderebilirsiniz. Ben bunun yerine bir partial response yazdım ve redirect işlemi gerçekleştirdim.

İhtiyacınız olacak örnek sınıf ve konfigürasyonları aşağıda bulabilirsiniz.

spring-security.xml
...
<http>
   <custom-filter ref="sessionManagementFilter" before="SESSION_MANAGEMENT_FILTER" />
</http>
<beans:bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
   <beans:constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
   <beans:property name="invalidSessionStrategy" ref="customRedirectStrategy" />
</beans:bean>
<beans:bean id="customRedirectStrategy" class="com.test.JsfRedirectStrategy"/>
<beans:bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/>
...

JsfRedirectStrategy.java
...
implements InvalidSessionStrategy
...
    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response){
        String redirectUrl = "invalid-session.xhtml";
        boolean ajaxRedirect = isAjaxRequest(request);
        if (ajaxRedirect) {
            if (request.getSession() != null) {
                HttpSessionRequestCache httpSessionRequestCache = new HttpSessionRequestCache();
                SavedRequest savedRequest = httpSessionRequestCache.getRequest(request, response);
                if (savedRequest != null) {
                    httpSessionRequestCache.removeRequest(request, response);
                }
            }
            String ajaxRedirectXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"" + redirectUrl + "\"></redirect></partial-response>";
            response.setContentType("text/xml");
            PrintWriter out = response.getWriter();
           out.write(ajaxRedirectXml);
           out.flush();
           out.close();
        } else {
            response.sendRedirect(redirectUrl);
        }
    }

Yukarıda mavi renkli kod bloğunu sebebi ise AJAX targetURL'i silmek. Yoksa bir sonraki tıklamada ekrana kaydedilmiş AJAX requesti basılıyor. Eski Spring Security versiyonlarında direkt olarak URL'i de silebiliyorduk fakat 3.1.3 versiyonunda ben SavedRequest'i silerek bu işlemi gerçekleştirdim.

Bir diğer önemli konu ise, yukarıdaki işlemi ben yalnızca session invalidate olurkenki süreçte gerçekleştirdim. Bir de daha önce invalide olmuş bir session varken gönderilen bir AJAX request olması durumu olabilir (Örneğin yeni bir sekmede uygulamaya devam ediyorsunuz, logout yapıp sessionu öldürdünüz fakat bir önceki sekmede bulunan açık olan sayfada ajax request gönderen bir linke tıkladınız). Bu tarz bir durumda ise AuthenticationEntryPoint sınıfını implement edip commence metodunu aynı şekilde oluşturmalısınız. Bu yöntemle, hiç bir requesti kaçırmayıp, kullanıcıyı istediğiniz adrese yönlendirebilirsiniz.

Bir üstteki paragrafa örnek sınıf:

CustomAuthenticationEntryPoint.java
...
implements AuthenticationEntryPoint
...
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        //Bir önceki sınıftaki kodun aynısı
    }

spring mvc, spring security, session management, invalid session, ajax redirect, jsf, redirect strategy, invalid session strategy )
Kaynak: http://code.google.com/a/apache-extras.org/p/ajax4click/wiki/ConfiguringSpringSecuritySessionTimeout
Kaynak: http://www.icesoft.org/wiki/display/ICE/Spring+Security

18 Şubat 2013 Pazartesi

Spring Security Session Management Concurrency Control Problemi

Springde session yönetimini Spring Security ile yapmak isteyenler, eş zamanlı session yönetimini de basit bir şekilde spring-security.xml dosyasını değiştirerek yapabilirler. Bu noktada, gerekli düzenlemeleri yaptıktan sonra bile aynı bilgiler ile login olan kullanıcı, session oluşturamaması gerekirken bu işlemi yapabiliyorsa, çok büyük ihtimalle UserDetails sınıfınızdaki bir problem ile karşı karşıyasınız.

Buradaki sıkıntı, Spring Security'yi kullanabilmek için gerekli olan default kullanıcı arayüzü olan UserDetails'in implement edilmesi esnasında yaşanıyor. Bu arayüzü implement ederken oluşturduğumuz  kendi kullanıcı sınıfımızda equals metodunu override etmemiz lazım. Çünkü bu metod içerisinde, sessionu oluşturan kullanıcıların concurrent olup olmadığını anlamamız için belirleyeceğimiz bir ya da birden fazla alanın karşılaştırılıp aynı olup olmadığının kontrol edilmesi gerekiyor. Bu metodu override ettikten sonra belirlediğimiz aynı alana sahip başka bir kullanıcı session oluşturmaya çalışınca Authentication hatası alacaktır.

Örnek vermek gerekirse;

Bir adet User sınıfımız olsun. Uygulamamızda bir kullanıcı session oluşturmuşken aynı kullanıcı adına sahip başka bir kullanıcının session oluşturmamasını istiyoruz. Örnek dosyalar aşağıdaki gibi olmalıdır:

web.xml
...
<listener>
  <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
...

spring-security.xml
...
<session-management invalid-session-url="/no-sess.xhtml">
  <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/no-sess.xhtml" />
</session-management>
...

MyUser.java
public class MyUser implements UserDetails
...
   @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyUser other = (MyUser) obj;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
  }
...

Bu konfigürasyonlar sonrasında bir kullanıcı session oluşturmuşken aynı kullanıcı adı ile başka bir session oluşturmaya çalışırsanız 'Maximum sessions of 1 for this principal exceeded' gibi bir hata mesajı alacaksınız.

spring mvc, spring security, session management, concurrency control, max-sessions, UserDetails )