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 )

7 Ocak 2013 Pazartesi

Zaman Çarkı (Elise) - Ken Grimwood


Evet, oldukça spoiler içerir şekilde:) 'Zaman Çarkı', orijial adıyla 'Elise' adlı kitaptan bahsedeceğim biraz.

Koridor Yayıncılık çıkışlı ve Ender Nail çevirili kitap Ken Grimwood tarafından yazılmış. Kitabın arka kapağındaki hikaye oldukça ilgiç gelmişti. Alma sebeplerimden birisi bu, diğeri de sanıyorum 'Olasılıksız' tarzı siyah beyaz, çekici kapağı oldu:) Kitapta 1600'lü yıllarda doğan ve sonrasında hiç yaşlanmayan bir kişnin hikayesi anlatılıyor. Kitap 2 ayrı senaryoda gidiyor. Bir tanesi 1600'lü yıllarda başlayıp devam eden, diğeri ise 1900'ün sonlarında devam eden iki hikaye olarak. Sonrasında tahmin edeceğiniz gibi bu hikayeler kesişiyor ve birleşiyor. Kem Grimwood'un bir kitabını ilk kez okudum. Açıkçası özellikle sürükleyici hikayelerde kahramanın ağzından olan anlatımı sevmediğim için bu kitabın anlatım kısmını oldukça iyi buldum. Yazar dışarıdan bir kişi olarak hikayeyi aktarıyor.

1600'lü yıllarda başlayan bölüm o yılların Fransa'sını kafanızda canlandırmanıza yetiyor ve sizi o tarihlere götürüyor. Romanlardaki en büyük sıkıntım olan farklı dillerdeki insan isimleri ve bu kişilerin saysının fazla olması bu noktada beni biraz zorladı açıkçası ama hikayeyi kaybetmeden devam edebildim. Ana karakterimiz bu yıllarda doğuyor ve ölümsüz yaşantısına başlıyor. Sevgilileri, eşleri oluyor. Onlar yaşlanırken ve ölürken kendisi genç olarak kalmaya devam ediyor. Bu hikaye, 1900'lü yılların sonralarında karakterin ismini Elise olarak değiştirmesi ve kendisini bu durumdan kurtarmasını umduğu bir profesör ile tanışması, durumunu anlatması ve çözüm araması kısmına geliyor. Çok da fazla detay vermek istemiyorum okumak isteyenler için. Genel hikaye bu şekilde.

Kitap, konunun ilginçliği ile birlikte sürükleyici başladı. Ana karakterin, Fransa'da o yıllarda yaşadıkları, kölecilik zamanları ve o zamanlardaki ayaklanmalar oldukça iyi aktarılmış. Özellikle karakterin, yaşlanmadığını keşfedip sevdiklerini birer birer kaybetmeye başlayınca hissettikleri ve büründüğü ruh hali, sizi de kolaylıkla etkiliyor. Fakat sonlara yaklaştığımda hikaye hızlanmaya başladı. Eskiden bölümler arası 1-2 yıl ilerlerken bir anda 10 yıl 20 yıl ilerlemeye başladık. Elise'in bir ülkedeki yaşantısı anlatılırken sonraki bölümde bir anda hiç alakası olmayan başka bir ülke ve yaşama geçti. Sanki yazar kitabın sonralına doğru sıkılmış da bir an önce bitirmek istemiş gibi. Tarih sayfalarında duyduğumuz isimlerle geçen o kadar güzel işlenmiş hikayeler, çok basit bir sonla bitti açıkçası. Kitabın ortalarına kadar oldukça heyacanlı okurken son bölümlerinde ise sıkıldım ve hayal kırıklığına uğradım. Fakat 3-4 gün içinde normal bir tempoda bitirilebilecek, fazla beklenti olmadan zaman geçirmek için fena olmayan bir kitap. Eğer 'The Man From Earth' filmini izlediyseniz ve hoşunuza gittiyse, bu kitabı da muhtemelen seveceksiniz.

zaman çarkı, elise, ken grimwood, Ender Nail, Koridor Yayıncılık, kitap, roman, inceleme )

21 Aralık 2012 Cuma

HttpURLConnection İle Web Sayfası İçeriği Almak

Java'da basit bir şekilde ve herhangi bir ekstra kütüphane kullanmadan HTTPURLConnection sınıfı ile bir web sitesine bağlanıp içeriğini almak konusunda ufak bir kod parçası ile örnek vereceğim. Bu işlemi, bir web sayfasını tamamen String olarak çekip parse etmek ya da GET, POST vs. ile çalışan bir REST servisi çağırmak için kullanabilirsiniz.

Aşağıdaki örnekte http://www.mehmetaktas.org?param1=VALUE1&param2=VALUE2 şeklinde bir adresin içeriğini çekeceğiz.

public String getURLContent(){
        String uri = "http://www.mehmetaktas.org?param1=value1&param2=value2";
        HttpURLConnection connection = null;        
        
        try {
            
            //Bağlantımızı açıyoruz
            URL url = new URL(uri);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");//POST, PUT, DELETE ...
            connection.setUseCaches(false);
            connection.setDoInput(true);
            connection.setDoOutput(true);
            /*
             * Content-Type, Content-Language gibi request property set etmek
             * istersek aşağıdaki metod kullanılabilir:
             * connection.setRequestProperty("Content-Language", "tr-TR");
             */
            //Response'ı alıyoruz
            InputStream is = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuilder response = new StringBuilder();            
            while ((line = rd.readLine()) != null) {
                response.append(line);
                response.append('\r');
            }
            rd.close();
            is.close();
            
           return response.toString();
            
        } 
        catch (Exception e) {} 
        finally {
            if (connection != null) {
                connection.disconnect();                
            }
        }
    } 

httpurlconnection, screenscraper, java, url, connection, web page, parse, rest, get, post )

13 Aralık 2012 Perşembe

Operation Not Allowed After ResultSet Closed

Merhaba,

JDBC kullanarak Java'da veritabanı bağlantısı yapıp veri çekmeye çalışırken "rs.next()"ile bir veriye ulaşırız. Bu metodu çağırdığımız esnada "java.sql.SQLException: Operation not allowed after ResultSet closed" şeklinde bir taha alıyorsanız olası bir kaç sebebi var. Bu sebeplerin başında ResultSet nesnenizi kullanırken bir döngü ya da başka bir metodun içerisinde "rs.close()" metodunu kullanarak ya da nesneyi null yaparak hata alma durumunuz vardır. Bu problemi, biraz araştırarak çözebilirsiniz fakat "Ben ResultSet nesnemi kesinlikle kapatmıyorum ya da null yapmıyorum neden böyle oluyor?" derseniz sebebi bulmak biraz zor olabilir. Böyle bir durum başıma geldi ve çözümünden bahsedeceğim.

Java tarafında veritabanına bağlanırken ResultSet nesnesini doldurmak için bir Statement ya da PreparedStatement nesnesi kullanırız. Bu nesne teoride bir adet ResultSet'e hizmet verebilir. Statement kapandığı zaman, ona bağlı olan ResultSet de kapanır. Sıkıntı da aslında buradan kaynaklanıyor. Özellikle uzun sürede sonuç getiren bir sorgu çalıştırdığınız zaman Statement, uygulama sunucusunun pool ayarlarında tanımlı olan Statement Timeout'una takılıyor. Bu esnada Statement, uygulama sunucusu tarafından kapatıldığı için ResultSet nesnesi de kapanmış oluyor. Siz de herhangi bir veri çekmeye çalıştığınızda en tepede bahsettiğim hatayı alıyorsunuz.

Bu sorunu, SQL sorgunuzu optimize ederek ya da uygulama sunucusundaki kullandığınız veritabanı pool'unun Statement Timeout süresini uzatarak çözebilirsiniz. Glassfish için bu ayara "Resources > JDBC > JDBC Connection Pools > POOL_ADI > Advanced" menüsünden ulaşabilirsiniz.

java, jdbc, statement, resultset, pool, timeout, sqlexception, connection )