7 Mayıs 2013 Salı

JSF ve HTML Escape Problemi, Çözümü

JSF teknolojisi, web uygulamaları geliştirirken standart olarak yazılımcının uygulaması gereken bir çok güvenlik açığını kendisi gidermektedir. Bunların en önemlilerinden birisi de XSS'i engellemek için render edilen HTML'de bulunan ve atak yapmaya müsait karakterleri escape etmesidir. Örneğin ">" şeklinde yazılan bir yazı, kaynak kodunda ">" şeklinde gözükecektir. Bu özellik güvenlik açısından kolaylık sağlasa da bir takım kısıtlamalar da getirmektedir. Örneğin, yazılımcının kendisinin, xhtml içerisine yazacağı javascript ya da benzeri kod blokları, JSF'in bu özelliği sebebi ile ya compile olmaz, ya da ekranda sorunlu bir şekilde render edilir.

Bu problemin çözümü ise <h:outputText> kullanmaktır. Bu tag ile, JSF'in default davranışını bypass edebiliriz. Örneğin basit bir Javascript tagi kullanalım:
<script>alert('Test');</script>
Bu kod bloğunu direkt olarak xhtml içerisine yazarsak &lt;script&gt;alert('Test');&lt;/script&gt; gibi bir görüntü ile karşılaşırız ya da IDE'miz bu kodu derlemez, hata verir. Bu kodu önyüze taşımak için:
<h:outputText value="&lt;script&gt;alert('Test');&lt;/script&gt;" escape="false" />
şeklinde bir kullanımda bulunmalıyız. Burada, h:outputText içerisindeki değerin escape="false" ile escape edilmemesini sağlıyoruz. Burada value'yu bu şekilde vermek yerine normal bir şekilde yazarak bir String halinde tutup: <h:outputText value="#{fooController.myScript}" escape="false" /> şeklinde de kullanabilirsiniz.

Görüldüğü üzere yöntem oldukça basit. Fakat benim başıma gelen bir problem, bu şekilde bir çözüme gitmeyi engelledi ne yazık ki. Sorun şu şekilde; Internet Explorer versiyonlarına göre farklı CSS dosyaları kullanmak için html taginin başına:
"<!--[if lt IE 7]> <html lang=\"en-us\" class=\"no-js ie6\" xmlns:h="http://java.sun.com/jsf/html"> <![endif]-->..." 
tarzı kodlar yazarız. Bu kodları, yukarıda bahsettiğim şekilde escape etmek ne yazık ki mümkün değil çünkü kullandığımız <h:outputText>'in dahil olduğu "http://java.sun.com/jsf/html" namespace'ini, henüz tanımlamadan bu tagi kullanmak istiyoruz ki bu da mümkün değil. Burada da imdadımıza <f:view> tagi geliyor. <f:view> taginin içerisine namespace'lerimizi tanımladıktan sonra bütün içeriğimizi (<html> tagi vs. de dahil), <f:view> içerisine koyuyoruz. Böylece içeride <h:outputText>'i kullanabilir duruma geliyoruz ve sorunumuz çözülmüş oluyor. Daha anlaşılır olması için:

Sorun: Aşağıdaki kod bloğunu escape etmeden ön tarafa taşımak
<!--[if lt IE 7]>          <html lang="en-us" class="no-js ie6"> <![endif]-->
<!--[if IE 7]>             <html lang="en-us" class="no-js ie7"> <![endif]-->
<!--[if IE 8]>             <html lang="en-us" class="no-js ie8"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en-us" class="no-js" xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"> <!--<![endif]-->
Çözüm: Kod bloğunu <f:view> içerisine alıp namespace'leri buraya taşımak
@Controller("fooController")
FooController{
   public String myUnescapedString =
 
                    "<!--[if lt IE 7]><html lang=\"en-us\" class=\"no-js ie6\"> <![endif]-->\n" +
                    "<!--[if IE 7]><html lang=\"en-us\" class=\"no-js ie7\"> <![endif]-->\n" +
                    "<!--[if IE 8]><html lang=\"en-us\" class=\"no-js ie8\"> <![endif]-->\n" +
                    "<!--[if gt IE 8]><!--> <html lang=\"en-us\" class=\"no-js\"> <!--<![endif]-->";
}
<f:view contentType="text/html"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core">
<h:outputText value="#{fooController.myUnescapedString}" escape="false"/>
</f:view>
( jsf, html, escape, h:outputText, f:view, unescape, xss )
Kaynak: http://stackoverflow.com/questions/10616944/jsf-2-1-ie-conditional-comments

7 Mart 2013 Perşembe

Ubuntu 12.10'a Oracle SQL Developer Kurulumu

Bu kurulum için ihtiyacımız olan bir kaç kurulum daha var. Öncelikle sistemde jre'nin kurulu olması gerekiyor. debhelper ve sqldeveloper-package paketlerine de ihtiyacımız var. Tabiki bir de ziplenmiş olarak indirdiğiniz SQL Developer'a ihtiyaç var.  Bu yazıda ben 'sqldeveloper64-3.2.20.09.87-no-jre.zip' kullanıyor olacağım.

sudo apt-get install sqldeveloper-package debhelper

Bu kodu çalıştırıp gerekli paketleri kurduktan sonra aşağıdaki komutla .deb dosyasını oluşturuyoruz:

make-sqldeveloper-package sqldeveloper64-3.2.20.09.87-no-jre.zip

Bu esnada make-sqldeveloper-package paketinde bulunan bir bugdan ötürü chmod: missing operand after `755' hatası alabilirsiniz. Bu hatayı çözmek için bu paketin kodunda ufak bir değişiklik yapmamız gerekiyor. Paketin versiyonuna göer değişiyor fakat 370 ile 385. satırlar arasında bir yerlerde bulunan aşağıdaki kodu, bir alttakiyle değiştirmeniz gerekiyor:

Silinecek Satır
${FIND} "${OPTDIR}" ! \( -type d -o -name "*.jar" \) |${XARGS} ${XARGS_OPTS} ${FILE} ${FILE_OPTS} |${GREP} ${GREP_OPTS} "shell script text executable" |${CUT} ${CUT_OPTS_FUNC_CLEAN} |${XARGS} ${XARGS_OPTS} ${CHMOD} ${CHMOD_OPTS}

Yerine Eklenecek Satır
${FIND} "${OPTDIR}" ! \( -type d -o -name "*.jar" \) |${XARGS} ${XARGS_OPTS} ${FILE} ${FILE_OPTS} |${GREP} ${GREP_OPTS} "shell script" | ${GREP} ${GREP_OPTS} "text executable" |${CUT} ${CUT_OPTS_FUNC_CLEAN} |${XARGS} ${XARGS_OPTS} ${CHMOD} ${CHMOD_OPTS}

Bu işlemden sonra hatadan kurtulmuş oluyoruz ve kodu yeniden çalıştırdığımızda .deb uzantılı dosyamız oluşmuş oluyor. Aşağıdaki şekilde .deb paketini açıyoruz:

sudo dpkg -i sqldeveloper.......deb

Böylece kurulum tamamlanmış oluyor. Dash Home'a "sqldeveloper" yazdıktan sonra uygulamamız açılacak. Yalnız yapmamız gereken son bir işlem daha var. Uygulama ilk kez açılırken size yüklü olan JRE'nin lokasyonunu soracak. Buraya da yüklediğimiz JRE'nin lokasyonunu yazacağız. Örneğin benimki şu şekildeydi:

/usr/lib/jvm/java-7-oracle

Artık IDE'miz kullanıma hazır durumdadır.

( ubuntu 12.04, oracle, sql developer, chmod, 755 )
Kaynak : https://bugs.launchpad.net/ubuntu/+source/sqldeveloper-package/+bug/985810

1 Mart 2013 Cuma

Spring MVC @Autowired NullPointerException

Spring MVC'de, nesnelerin yönetimini framework'e bırakma olayı sıkça kullanılan bir durumdur ve Spring'in ana kullanım amaçlarından birisidir. Bu kullanımlar esnasında, @Autowired annotation'unu kullanarak ya da getBean() metodu ile nesnelere ulaşmak istediğimizde alınan NullPointerException da oldukça sık karşılaşılan bir durumdur:) Ben yazının geri kalanında Annotation kullanımı ile devam edeceğim.

Burada, hataya sebebiyet verecek milyonlarca ihtimal vardır. Internette araştırdığınızda da bir dolu yazı karşınıza çıkacaktır. Bu sebeplerden bir kaçı:

  • @Autowired kulllandığınız nesneye sahip sınıfın Spring tarafından inject edilmemiş olması. Yani bu nesneye ait sınıfın başında @Component, @Controller gibi Spring'e özgü ve o sınıfın Spring tarafından yönetildiğini belirten annotationlar kullanılmalıdır.
  • Annotation kullanılan durumlarda, springContext.xml içerisinde '<tx:annotation-driven />' ve '<context:component-scan base-package />' taglerinin kullanılması gerekir.
  • Başına @Autowired eklediğiniz bir nesneyi 'new MyClass()' şeklinde kendiniz oluşturmamalısınız; bu nesnenin yönetimi artık Spring'dedir.

Yukarıdaki maddelere eklenecek başka maddeler de vardır. Bu hatalardan bir ya da birkaçına düştüyseniz kullanmak istediğiniz nesnenin null olması ihtimal dahilindedir. Peki herşeyi denediniz, bütün kodlama doğru ve hala bu hatayı alıyorsanız? O zaman benim karşılaştığım durumla karşılaşmış olma ihtimaliniz var. Spring MVC, kontrolüne verdiğiniz sınıfın instance'ını oluştururken, constructor'ı çalıştırma esnasında autowired nesneyi henüz inject etmiyor. Dolayısıyla bu nesneyi consturctor içerisinde kullanıyorsanız NullPointerException alıyorsunuz. Bu sorunun çözümü ise, eğer sınıfın instance'ını oluştururken yapmanız gereken bir iş yok ise, constructor içerisindeki bütün kod bloğunu @PostConstruct annotation'una sahip bir metoda vermeniz. Böylece ilgili nesne Spring tarafından oluşturulmuş ve kullanıma hazır hale gelmiş olacaktır. Aşağıda örnek kod parçalarını bulabilirsiniz.

Yanlış Kullanım

@Controller("userController")
public class UserController implements Serializable{
    @Autowired
    transient private SessionController sessionController;
    
    public UserController() {
        System.out.println(sessionController.getActiveUser().getUserName());
    }
}

Doğru Kullanım

@Controller("userController")
public class UserController implements Serializable{
    @Autowired
    transient private SessionController sessionController;
    
    public UserController() {}
    
    @PostConstruct
    public onLoad() {
        System.out.println(sessionController.getActiveUser().getUserName());
    }
}

spring mvc, injection, autowiring, @autowired, nullpointerexception, constructor, postconstruct )

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 )