JSF

JSF Dersleri-Spring Security İle Yetkilendirme Ve Kimlik Doğrulama

Merhabalar arkadaşlar. Bu dersimizde Spring Security kullanarak sayfalara erişim yetkisi tanımlamayı öğreneceğiz.

Spring Security Nedir?

Spring Security en basit tabiri ile Spring Framework’ün gelişitiricisi şirket tarafından yazılmış kimlik doğrulama ve yetkilendirme mekanizmasına sahip olan bir güvenlik uygulamadır.

Nelere İhtiyacımız Var?

Bu dersimizde Spring Security kullanımı için bir JSF destekli bir Maven projesine ve ona ilave bağımlılıklara ihtiyacımız var. Bir Maven projesi açıp ona JSF desteği eklemeyi şurada anlatmıştım. JSF destekli Maven projesi edindikten sonra pom.xml dosyasını açın, ona bazı bağımlılıklar ekleyeceğiz. Ekleyeceğimiz bağımlılıklar Spring, Spring Security ve Commons Logging’e aittir. Aşağıdaki kodu pom.xml’inizdeki bağımlılıklarınıza ekleyin.

       <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${security.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${security.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-taglibs</artifactId>
            <version>${security.version}</version>
        </dependency>
        
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>

Bu kodun düzgün çalışabilmesi için properties etiketleri arasında şu tanımlamaların da olması gerekiyor. Muhtemelen pom.xml’de properties etiketi hazır gelmiş olacağı için sizin versiyon etiketlerini alıp koymanız yeterli olacaktır.

   <properties>
        <spring.version>4.0.2.RELEASE</spring.version>
        <security.version>3.2.1.RELEASE</security.version>
    </properties>

Netbeans ortamında pom.xml’i kayıt edip projeyi build etmeniz gerekiyor, Eclipse’de de pom.xml kaydediğildiğinde bağımlılıklar direk indiriliyor.

Şu anda kütüphanlerimiz geldiği için Spring Security için ayarlamalarımıza geçebiliriz. Şimdi projenizin WEB-INF klasörü altına SpringConfiguration.xml adında bir dosya koyun ve içine aşağıdaki kodu alın.

SpringConfiguration.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                            http://www.springframework.org/schema/jdbc 
                            http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
                            http://www.springframework.org/schema/security 
                            http://www.springframework.org/schema/security/spring-security-3.2.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd">
                            
    <security:http auto-config="true" use-expressions="true">

        <security:intercept-url pattern="/index.xhtml" access="permitAll" />
        <security:intercept-url pattern="/icerikEkle.xhtml" access="hasAnyRole('ROLE_ADMIN', 'ROLE_GUEST')" />
        <security:intercept-url pattern="/onizleme.xhtml" access="hasAnyRole('ROLE_ADMIN','ROLE_GUEST')"/>

        <security:form-login login-processing-url="/j_spring_security_check" login-page="/girisYap.xhtml"
                             default-target-url="/index.xhtml" authentication-failure-url="/girisYap.xhtml"/>
        <security:logout invalidate-session="true"  logout-success-url="/girisYap.xhtml" />
        <security:remember-me data-source-ref="dataSource" token-validity-seconds="86400" />

    </security:http>

    <security:authentication-manager>

        <security:authentication-provider>
            <security:password-encoder hash="md5"/>
            <security:jdbc-user-service data-source-ref="dataSource"
                                        users-by-username-query="select kullaniciAdi,password, enabled from uyeler where kullaniciAdi=?"
                                        authorities-by-username-query="select u.kullaniciAdi, r.role from uyeler u, userroles r
                                        where u.kullaniciAdi = r.kullaniciAdi and u.kullaniciAdi =?"/>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/BlogProjesi"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>

    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>messages</value>
            </list>
        </property>
    </bean>
     
</beans>

Bu xml dosyamızda kimlik doğrulama ve yetkilendirme mekanizmamızı tanımlıyoruz arkadaşlar. Kodun 16-27 satırları arasına bakalım. <security:intercept-url /> etiketi ile yönetmek istediğimiz, erişimi kısıtlayıp yetkilendirmek istediğimiz sayfaları tanımlıyoruz.  18.satırdaki kod index.xhtml sayfası için bir tanımlama yapmakta ve access özelliğine permitAll ataması yapılarak bu sayfaya herkesin erişebileceği söyleniyor.  Yani kimin erişeceği access özelliği ile belirleniyor. 19.satırdaki kod içerik ekleme sayfası için bir tanımlama yapıyır ve access özelliğine hasAnyRole(‘ROLE_ADMIN’, ‘ROLE_GUEST’) ataması yapılıyor. Bu giriş yapmamış, oturum açmamış bir kullanıcının bu sayfaya erişemeyeceğini söyler ve giriş yapanlar içinden de admin ve guest rolündeki kullanıcıların erişebilceğini bildirir.

22.satırdaki security:form-login etiketi ile oturum açma sistemini tanımlıyoruz. login-processing-url özelliği ile kullanıcının oturum açarken girdiği kullanıcı adı şifrenin hangi url üzerinde işleneceğini belirtiyoruz. Bu özellik Spring Security’nin varsayılan ayarlarında /j_spring_security_check  değerindedir. login-page özelliği ile hangi sayfanın oturum açma sayfası olduğunu söylüyoruz. default-target-url özelliği ile kullanıcı giriş yaptığında hangi sayfaya yönlendirileceğini söylüyoruz. Bu örneğimizde kullanıcı giriş yapınca index sayfasına yönlendirilecek. authentication-failure-url  özelliği kullanıcı giriş yapmayı denediği fakat geçersiz kullanıcı adı şifre girdiğinde yönlendirilecek sayfa belirtiliyor.

24.satırdaki <security:logout/> etiketi ile çıkış yapıldığında ne olacağını tanımlıyoruz. invalidate-session özelliğine true atıyarak çıkış yapıldığında session da kapattırılıyor. logout-success-url ile çıkış yapıldığında hangi sayfaya yönlendirileceğini söylüyoruz.

29-38 satırlar arası bu işin bel kemiğini oluşturuyor. Kimlik doğrulama işlemini yönetmesi için <security:authentication-manager> etiketini açıyoruz ve içine yetkilendirme sağlayıcısı olarak <security:authentication-provider> etiketi açıyoruz.  <security:password-encoder hash=”md5″/> etiketi ile kullanıcının girdiği şifresi MD5 şifreleniyor ve veri tabanında eşleşiyor mu diye bakılıyor. <security:jdbc-user-service> etiketi ile de asıl mana da yetkilendirmeyi yapıyoruz. data-source-ref özelliği ile verilerin nereden çekilip karşılaştırılacağını söylüyoruz. users-by-username-query özelliğine atadığımız sql sorgusu ile giriş yapmayı deneyen kullanıcının bilgilerini uyeler tablosundan alıyoruz ve kontrol ediyoruz. authorities-by-username-query  az önce bilgileri çekilmiş kullanıcının yetki tipini öğreniyoruz.

40-45 arasındaki satırlar ile veritabanına ait atamalar yapılıyor. Burada Spring’in jdbc için sağladığı sınıfı ve veritabanın jdbc sınıfını birlikte kullandığımıza dikkat edelim.

47-53 arasındaki satırlar ile Spring’in döndürdüğü hataların ekrana yazdırılabilmesi için bir properties dosyasının kullanımı ile bilgiler yer alıyor.

Web.xml

 <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/SpringConfiguration.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
  
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
  </filter-mapping>

Web.xml dosyamız içinde Spring Security için hazırlamış olduğumuz xml dosyamızı tanıtmamız gerekiyor. Bunun için context-param etiketini kullanıyoruz. Dosyayı tanıttıktan sonra bu dosyadaki verileri işleyecek sınıfları tanıtmamız gerekiyor. Bu sınıflarda ContextLoaderListener ve RequestContextListener sınıflarıdır. listener etiketi ile bu sınıfları tanımlıyoruz. Son olarak da Spring Security’nin filtre sınıfını tanıtıyoruz. DelegatingFilterProxy filterını ve ona ait pattern’i tanımlayıp kod tarafına geçebiliriz.

girisYap.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"> 

<h:head>
	<title>Giriş Yapın</title>
	<f:event listener="#{girisDenetlemeSinifi.authorizedUserControl()}" type="preRenderView"/>
</h:head> 
<h:body> 
    <h:form prependId="false">
        <center style="margin-top:50px;">
            <p:panelGrid columns="2">
                <f:facet name="header">
                    <h:graphicImage name="jsfblog.png" library="images"/>
                </f:facet>
                <h:outputText value="Kullanıcı Adınız:"/>
                <p:inputText value="#{uyeBilgileri.girilenKullaniciAdi}" id="j_username" required="true" requiredMessage="Kullanıcı Adı Boş Geçilemez"/>

                <h:outputText value="Şifreniz:" />
                <p:password id="j_password" required="true" requiredMessage="Şifre Kısmı Boş Geçilemez"/>

                <h:outputText value="" />
                <p:commandButton value="Giriş Yapın" action="#{girisDenetlemeSinifi.login()}" actionListener="#{uyeBilgileri.bilgileriGetir}" ajax="false"/>

                <h:outputText value="" />
                <p:button value="Kayıt Olun" outcome="kayitOl.xhtml"/>
            </p:panelGrid>
        </center>
        <h:outputLabel value="#{sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}" rendered="#{!empty sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}"
                               style="margin-left: 5px;color: red"/>
    </h:form>
</h:body> 
</html>

Xhtml sayfamız içinde ilk olarak head etiketlerinin arasında f:event etiketi tanımlıyoruz ve type özelliğine preRenderView ataması yapıyoruz. listener özelliğine de giriş denetleme sınıfnın içindeki authorizedUserControl metodunu atıyoruz. Böylece sayfa kullanıcıya gösterilmeden hemen önce authorizedUserControl metodu çağırılıyor.

Bunun dışında yapmamız gereken bir diğer işlem gerekli id atamalarının yapılmasıdır. Kullanıcı adının girildiği inputText’in id özelliğine j_username  ,parolasını girdiği inputText’in id özelliğine de j_password atamasını yapıyoruz.

Giriş Yapın butonu ile de kullanıcının girdiği kullanıcı adı, parolanın kontrol edilceği metodu çağırıyoruz.

Alt kısımda dikkat ederseniz bir de outputLabel bileşeni bulunmakta.  Bu outputLabel Spring Security’nin son fılattığı alıyor, properties dosyasındaki karşılığını buluyor ve sonra ekrana bunu yazıyor.

GirisDenetlemeSinifi.java Kodu

package com.ilkgunel.controller;

import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.application.NavigationHandler;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

@ManagedBean
@SessionScoped
public class GirisDenetlemeSinifi implements Serializable{
	
	public void login()  
	{

        System.out.println("Login Metoduna Girildi");
        try {

            ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
            RequestDispatcher dispatcher = ((ServletRequest)context.getRequest()).getRequestDispatcher("/j_spring_security_check");

            dispatcher.forward((ServletRequest)context.getRequest(), (ServletResponse)context.getResponse());
            FacesContext.getCurrentInstance().responseComplete();
           
        } catch (ServletException | IOException ex) {
            Logger.getLogger(GirisDenetlemeSinifi.class.getName()).log(Level.SEVERE, null, ex);

        } 
        /*finally {
            return null;
        }*/
        System.out.println("Login Metodundan Çıkıldı");
    }
	
	public void authorizedUserControl(){

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if(!authentication.getPrincipal().toString().equals("anonymousUser")){

            NavigationHandler nh =  FacesContext.getCurrentInstance().getApplication().getNavigationHandler();
            nh.handleNavigation(FacesContext.getCurrentInstance(), null, "/icerikEkle.xhtml?faces-redirect=true");

        }
    }
}

Giriş denetleme sınıfımız içinde iki metodumuz bulunuyor.  Login metodu kullanıcının girdiği kullanıcı adı şifreyi Spring Security’e iletip mekanizmayı çalışıtırıyor. authorizedUserControl metodu az önce girisYap sayfasında da gördüğümüz üzere sayfa yüklenmeden hemen önce çağırılıyor ve kullanıcının giriş yapıp yapmadığına bakıyor. Eğer yapmışsa girisYap sayfası yerine içerik ekleme sayfasına yönlendirme yapılıyor ve bu güvenlik çatısı giriş sayfasını kendisi kapatmış oluyor. Yani oturum açıldığında giriş sayfasına artık ulaşılamıyor.

messages.properties

AbstractUserDetailsAuthenticationProvider.badCredentials=Geçersiz Kullanıcı Adı Veya Şifre Girdiniz
AbstractUserDetailsAuthenticationProvider.disabled=Hesabınız Henüz Aktive Edilmemiş Ve Sistem Yöneticisi Hesabınızı Engellemiş

Properties dosyamız içinde hesabın engellenmiş olması ya da kullanıcı adı şifrenin yanlış olması ile mesajların Türkçe karşılıkları yer alıyor.

 Ekran Çıktıları

Screen Shot 2015-08-24 at 14.09.34

Veritabanında yer alan kullanıclar, MD5 ile şifrelenmiş parolaları ve onların yetkileri.

Screen Shot 2015-08-24 at 14.11.18

Mysql’de yazılmış bir trigger vasıtası ile kulalnıcının yetkisi ayrıca bir de userRoles tablosunda tutuluyor.

Screen Shot 2015-08-24 at 14.13.28

İçerik ekleme sayfasına ulaşmaya çalışıtığımda giriş sayfasına yönlendiriliyorum.

Kayıtlı bir kullanıcı ve parolamı giriyorum. Parolam MD5 şifrelenip veritabanında karşılaştırılacak.

Kayıtlı bir kullanıcı ve parolamı giriyorum. Parolam MD5 şifrelenip veritabanında karşılaştırılacak.

Screen Shot 2015-08-24 at 14.15.46

Giriş yaptığımda beni girisYap sayfasındaki preRenderView metodu sayesinde içerik ekleme sayfasına yönlendirildim.

Screen Shot 2015-08-24 at 14.28.29

Geçersiz bir kulalnıcı adı şifre girdiğimde Spring Securty mesaj döndürüyor ve bunu bildiriyor.

Spring Security kullanımı da bu şekilde arkadaşlar. Kullanımı biraz zor gelmiş olabilir, ben de ilk kullanmaya çalıştığımda çok zorlanmıştım. Yazıda anlamadığınız yer olursa, veya ilaveten sormak istedikleriniz olursa yorum kısmından sorabilirsiniz. Gelecek JBoss Seam Security kullanımını anlatmayı planlıyorum. Görüşene kadar sağlıcakla kalın 🙂

4 Yorum

  • Dostum selam yazılarını takip ediyorum oldukça iyi kaynaklar. Benim bir sorunun var belki bir yol gösterebilirsin, malum böyle projelerde kütüphanelerin güncel olması çok önemli fakat senin şuanki sürümle spring security 4.0.3 sürümü uyumlu değil ve default olarak “Bad cridentials” hatası alınıyor, çözümü mutlaka vardır fakat yinede bulamadım, projeni güncellersen yada aynı projenin “xml” değilde “annotations” ile yapılmışını paylaşabilirsen çok daha iyi olacağını düşünüyorum. Yazılarının devamını dilerim, teşekkürler.

    • Merhabalar.
      Spring’in 4.x.x numaralı sürümü ile Spring Security’nin 4.x.x numaralı sürümünü birlikte kullanmak için ben de uğraştım ama bir çözüm bulamadım, dediğin gibi hata alıyordum. Ama yakın zamanda denemedim, tekrar bakmam gerek 🙂
      Annotations meselesine bakayım inşaAllah, bu yazının ikincisi olabilir belki o.
      Ben de teşekkür eder, sağlık sıhhat dilerim.

  • Güzel ve açıklayıcı bir paylaşım.
    source kodlarını da paylaşırmısın. anotation lar ile de yapmışsan paylaşımlarını bekliyoruz 🙂

Erdem Kalyoncu için bir yanıt yazın X