Sansür, bir toplumun kendine olan güvensizliğini yansıtır ve otoriter rejimlerin belirgin bir özelliğidir.

--Potter Stewart

25.11.2011

JSF ve Spring Security ile Önceden Kimlik Kontrolü Yapılmış Kullanıcı için Login Sayfasının Tekrar Gösterilmemesi

Başlık biraz uzun oldu ama anlatacağım konuyu başlıkta ancak bu kadar özetleyebildim. Özetin açıklamasını yapmaya çalışırsak: web uygulamasına giriş yapmış, yani kimliğini doğrulatmış bir kullanıcı, belirli sayfalara istekte bulunduğunda (örneğin login sayfası için tekrar istek gönderirse) zaten yetkilendirildiğini belirleyip isteği başka bir sayfaya yönlendirmek (örneğin login sayfasından sonra gösterilecek index sayfasına) istiyoruz.

Spring Security'de bu işin kolay yolu var mıdır diye küçük bir araştırma yaptık ama ya doğru kelimeleri yine bulamadık ki blog başlığı seçerken de yaşadım aynı sıkıntıyı ( :) ) ya da diğerler geliştiriciler böyle bir gereksinime ihtiyaç duymamışlar. İlki seçenek akla daha yatkın..

Çalışma arkadaşım Basri Kahveci'nin önerdiği ve bir projede uyguladığı yöntem de aklıma yatmayınca (Çalışıyor ama biraz kulağı tersten tutmak gibiydi :)) Küçük bir "Nasıl yapabiliriz?" düşünmesinin ardından aslında basit bir JSF PhaseListener kullanarak yapılabileceğini farkettim.

Bildiğiniz üzere JSF yaşam döngüsü temel olarak 6 adımdan oluşur (Yaşam döngüsünü detayıyla incelemek isterseniz BalusC bu konuda emek harcamış):




Kısaca hatırlamak gerekirse Request parametresi barındıran bir istek bu 6 adımdan geçerek kullanıcıya hedef sayfa döndürülür. Bir sayfaya yapılan ilk istek, yani parametre barındırmayan bir istek ise ilk adımdan sonra yaşam döngüsünü dolaşmak yerine Render Response adımına yönlendirilip kullanıcının istediği sayfa gösterilir. Her adımda gerçekleşen işlemleri isterseniz başka bir blog girdisinde bahsederiz.

JSF bize bu adımların arasına girebilmek için PhaseListener interface'ini kullanıma sunuyor. Görüldüğü gibi PhaseListener'da 3 metot mevcut. İkisi yaşam döngüsü adımlarının herbirine girerken ve herbirinden çıkarken işletilecek kodları barındırırken, üçüncü metot da hangi adımda bu listener'ın çalıştırılması gerektiğini belirtmemize olanak sağlıyor. Bu metotta PhaseId.ANY_PHASE gibi bir değer döndürerek her bir adım için listenerın tetiklenmesini sağlayabileceğimiz gibi PhaseId.RENDER_RESPONSE benzeri bir değerle belirli bir adımda tetiklenmesini de sağlayabiliyoruz.

Bizim senaryomuzda da login sayfamız render olmadan önce bir PhaseListenerın tetiklenmesini sağlayarak, kullanıcının önceden sisteme giriş yapıp yapmadığını belirleyip gerekiyorsa başka bir sayfaya yönlendirme işlemini yapmak mümkün:

public class AuthPhaseListener implements PhaseListener {

 @Override
 public void afterPhase(PhaseEvent event) {
 }

 @Override
 public void beforePhase(PhaseEvent event) {
  User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  if(user != null){
   FacesContext.getCurrentInstance().getExternalContext().redirect("main");
  }

 }

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

}

beforePhase() metodu içinde Spring Security'nin SecurityContextHolder'ı ile sessionda kayıtlı bir kullanıcı  olup olmadığını kontrol ettik. Eğer bir kullanıcı nesnesi elde edebiliyorsak FacesContext ile de basitçe yönlendirme işlemini yapıyoruz.

Geriye yazdığımız bu listener sınıfını projemize entegre etmeye geldi. Eğer listener tanımını faces-config.xml içerisinde yaparsak her sayfa için işletilecek yaşam döngüsünde sınıfımız tetiklenecektir. Bizim istediğimiz sadece login sayfasında bunu tetiklemek. O yüzden tanımı listenerı kullanmak istediğimiz sayfa bazında yapmamız gerekli. JSF sayfamıza şu tag'i eklemek yeterli:

<f:phaseListener type="com.prime.dasgin.AuthPhaseListener"/>


JSF kullanılmayan projelerde benzer bir mantık web.xml'de tanımlanan Servlet Filter'lar ile de gerçekleştirmek mümkündür. Login sayfasına yapılacak olan bir istekte devreye girecek bir Filter tanımlamak ve benzer bir süreci içinde işletmek sorunu çözer.



1 yorum:

  1. Merhabalar,

    Bu problem çoğu yerde bizimde karşımıza çıkıyor. Müşterilerimiz login olduktan sonra herhangi bir nedenle tekrar login sayfasına tıkladıklarında login penceresi yerine örneğin, ana sayfayı görmek istiyorlar. Malesef Spring Security'nin buna yönelik bir çözümü yok. Olmasını da beklemek çok doğru gelmiyor, çünkü Spring Security'nin sorumluluk alanına giren de bir ihtiyaç değil.

    Aslında bahsettiğin ServletFilter tabanlı çözüm JSF kullanan uygulamalar için de geçerli.

    Spring Security bildiğiniz gibi ServletFilter'lar üzerine kurulu bir framework. JSF requestlerini ele alan FacesServlet ve diğer servlet veya resource'lardan önce request'i intercept ederek güvenlik işlemlerini gerçekleştiriyor.

    Basit bir ServletFilter yazarak gelen request'in /login.jsp ile eşleşip eşleşmediğini kontrol ettikten sonra SecurityContext'de de geçerli bir Authentication nesnesinin olduğunu test ederek request'i index.jsp'ye yönlendirebilirsiniz. JSF'in de buna hiç bir itirazı olmayacaktır :-)

    Buradaki önemli nokta yazdığımız RedirectFilter'ın Spring Security Filter zincirinde hangi Filter'dan sonra devreye gireceği. Filter'ımız SecurityContext'i HttpSession'dan alarak SecurityContextHolder'a koyan SecurityContextPersistenceFilter'dan sonra devreye girmeli.

    Kenan Sevindik

    YanıtlaSil