Spring Security

  • Spring security ile ilk tanışacaklar için daha önce hazırlamış olduğum Spring Fundamentals ve Spring Boot yazılarımı okumalarını rica ediyorum

Spring Security Nedir ?#

  • Spring Serurity ana hatlarıyla bir sürü servlet filtresi içeren bir frameworkdür.

  • Aynı zamanda Spring Web MVC ve Spring Boot gibi diğer spring famework leri ile çok uygun bir şekilde çalışmaktadır.

  • Geliştiriciye sunduğu web konfigürasyonları ile oluşturulan uygulamanın tehtitlere karşı güvenli olmasını sağlar.

Bir Web Uygulamasındaki Güvenlik Terimleri#

Spring Security kapsamlı bir şekilde anlamak açısından bilmemiz gereken 3 tane ana konu bulunmaktadır. Bunlar;

  1. Authentication (Kimlik Doğrulaması)
  2. Authorization (Yetkilendirme)
  3. Servlet Filters

Authentication (Kimlik Doğrulaması)#

  • Çoğunlukla Web Uygulamaların bir kullanıcı kontrolü yapılmaktadır. Giriş yapmak isteyen kişinin doğruluğunun kontrol edilmesi gerekir. Bu kontrol de genellikle Kullanıcı adı ve Şifre ikilisi ile yapılır.
  • Çok katlı bir binaya girdiğinizi düşünün. Kimlik Doğrulaması nı bu binanın giriş kısmındaki bölüm olarak düşünebilirsiniz.

Authorization (Yetkilendirme)#

  • Küçük ve basit uygulamalarda çoğunlukla sadece Kimlik Doğrulaması yeterli olacaktır. Kullanıcın kimliği doğrulandıktan sonra kullanıcı uygulamadaki her kısıma ulaşabilmektedir.
  • Ancak, büyük ölçekli ve kullanıcının erişimi için İzin ve Rol bilgisi gerektiren durumlar ortaya çıkacaktır.
  • Az önce verdiğimiz örnektedi gibi bir binaya girdiğimiz düşünelim. Kimlik Doğrulama mız yapıldı ancak her kata Erişim imiz yok. İşte bu erişim durumunu bu şekilde düşünebilirsiniz.

Servlet Filter#

  • Servlet’i bu başlık altında çok derin olarak anlatmam doğru olmayacaktır. Ancak, basit ve uygun şekilde basitleştirmek gerekirse; Servlet’ler, Java dünyasında bir web uygulaması oluşturmak için olmazsa olmaz olan bir API’dir. Uygulamadaki HTTP isteklerini bu servlet’ler kontrol ederler. Spring ise bu Servlet’lere bir implemantasyon hazırlamıştır. Adı da DispatcherServlet dir. Spring bu Core servlet kütüphanelerini DispatcherServlet interface vasıtasıyla kullanır.
  • Daha detaylı bilgi için Spring Dökümantasyonu

Hala benimle iseniz devam edelim xd

Peki Servlet Filter Nedir ?

  • Web uygulamalarında kullanıcı adı ve kullanıcı şifresi ya da token bilgileri genellikle HTTP Header içerisinde gönderilirler. Durum böyle olunca sizin oluşturuduğunuz web uygulamasında herhangi bir kimlik doğrulaması işleminde @Controller, @RestContoller anotasyonlu controller larınızda methodlar içerisinde kimlik bilgilerini kontrol etmeniz gerekecek. Ancak şöyle bir durum var ki HTTP isteği çoktan sizin controller’ınıza ulaşmış olacak ve bu senaryo doğru değil.

  • Böyle bir kontrol yapmak yerine her istek için, istekler bir filtreden geçip sizin controller’larınıza gelmeli.

  • Java’da böyle bir durumu oluşturabilmemiz Filter‘lar mevcut. Bu Filterı servlet imizin önüne konuçlandırabiliriz.

Servlet Filter

Örnek bir Filter class’ına bakalım;

import javax.servlet.*;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SecurityServletFilter extends HttpFilter {

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        .
        .
        .
        .
    }
  • Yukardaki kod bloğu istenilen filtreyi uygulayacaktır. Ancak şöyle bir durum var ki, uygulama giderek büyüyünce ve daha çeşitli güvenlik kontrollerine ihtiyaç oldukça yeni yeni filtreler ekleme gereksinimi oluşacaktır.

  • Hal böyle olunca, gelen HTTP isteği bir filtreden çıkıp diğer filtreye ve ardından başka bir filtreye geçebilme durumu söz konusu oluyor. Bu duruma Filter Chaining adı veriliyor.

Örneğin uygulamanızda sırasıyla;

  1. Kullanıcı için sizin login sayfanıza ulaşıp ulaşamaması için oluşturulan LoginFilter
  2. Ardından kimlik doğrulaması için AuthenticationFilter
  3. Ardından da yetki doğrulamaları için AuhtorizationFilter
  4. En son olarak conttoller’a ulaşan istek.

gibi bir filtre trafiği oluşabilir.

Buraya kadar Spring Security‘e dair birşey konuşmadık. Ancak yukarıda bahsettiğim ön bilgiler vasıtasıyla Spring Security nin içinde nasıl çalıştığını anlamanız kolaylaşacaktır.

Spring Security’e Giriş#

  • Spring Boot ile oluşturduğunuz bir projeye Spring Security entegrasyonu yapıp çalıştırdığınızda aslında arkada, Spring Security‘nin içinde hazır bir şekilde bekleyen yaklaşık 15 tane ServletFilter uygulamanıza inject ediliyor.

  • Sping Security‘i uygulamanıza dahil etmeniz ve uygulamayı çalıştırdığınız taktirde, aşağıdaki gibi bir spring security logu göreceksiniz

INFO 1503 --- [  restartedMain ] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@791539ac, org.springframework.security.web.context.SecurityContextPersistenceFilter@56cd93ec, org.springframework.security.web.header.HeaderWriterFilter@747ce708, org.springframework.security.web.csrf.CsrfFilter@43213d9b, org.springframework.security.web.authentication.logout.LogoutFilter@6e02dda8, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@29996ad9, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@22be4c29, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@3a84d7ce, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@718584a0, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4b172313, org.springframework.security.web.session.SessionManagementFilter@1ef6540, org.springframework.security.web.access.ExceptionTranslationFilter@742a8e6b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@c74844]
  • Yani gelen bir HTTP isteği 15 adet filtreden geçip sizin @RestController‘ınıza ulaşıyor.

Bu hazır filtrelerden bazıları şu şekilde;

BasicAuthenticationFilter: Gelen HTTP isteğinin içerisinde Basic Authentication Header‘ı arar ve bulursa header da verilen username, password ikilisi ile kimlik doğrulaması yapar.

UsernamePasswordAuthenticationFilter: Gelen HTTP isteğinin içerisinde username/password ikilisini, parametrede ya da body’de arar ve bulursa kimlik doğrulaması yapar.

DefaultLoginPageGeneratingFilter: Uygulama için hazır bir login ekranı oluşturur.

DefaultLogoutPageGeneratingFilter: Uygulama için hazır bir logout ekranı oluşturur.

FilterSecurityInterceptor: Authorization (Yetkilendirme) işini halleder.

Sonuç olarak buradaki hazır filtreler uygulama için geçerli olacak güvenlik yeteneklerini getirir. Bu durumda bize kalan da bu filtrelerin konfigürasyonlarını yapmak ve uygulama için bu filtreleri kullanılır hale getirmektir.

Spring Security Konfigürasyonları#

  • Spring Security‘i projeye entegre etmek için gerekli olan iki ana adım mevcut. Bunlar;
  1. Konfigürasyon sınıfına @EnableWebSecurity anotasyonu eklemek.
  2. Konfigürasyon sınıfının WebSecurityConfigurerAdaptersınıfını extend etmesi gerekmektedir. Bu sınıf sayesinde belirtilen URI üzerinde güvenlik kontrollerini kontrol edebileceksiniz.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception { 
      http
        .authorizeRequests()
        .antMatchers("/api/auth/**").permitAll() // (1)
        .anyRequest().authenticated() // (2)
        .and()
        .formLogin() // (3)
        .loginPage("/login")
        .permitAll()
        .and()
        .logout() // (4)
        .permitAll()
        .and()
        .httpBasic(); // (5)
  }
}
  1. /api/auth/** URI’sine gelen tüm istekler Spring Security tarafından servlete iletilecektir. Kullanıcının bu URI’lere erişimi için herhangi bir kimlik doğrulamasına tabi tutulması gerekmez. antMatcher kullandığımız için bu alana wildcard semboller(*, /**) kullanılabilmektir.

  2. Birinci kuralda belirtilen istek URI’ları dışında doğer gelen tüm istekler Spring Security tarafından kullanıcının kimlik bilgisi olmadan erişime izin verilmeyecektir.

  3. Kullanıcı Adı / Şifre ikilisi ile giriş yapılabilecek /login URI’ı üzerinde bulunan bir login sayfasına erişim izni veriliyor. Herkes login sayfasına erişim sağlayabilecek.

  4. Aynı şekilde çıkış sayfası için de herkesin erişimi olacak.

  5. Kimlik bilgilerinin HTTP basic header’ı içinde gönderileceği izni.

Spring Security ile Kimlik Doğrulaması#

  • Kabaca bir web uygulamasında kimlik doğrulaması yapmak için 3 tane yöntem gösterilebiril. Bunlar;
  1. Veritabanı ile: Kullanıcının şifrelenmiş bilgilerini (Username / Password) alıp veritabanındaki bilgileri ile karşılaştırmak.
  2. 3rd Party ile: Kullanıcının şifrelenmiş şifre bilgisine erişiminiz yoktur. 3rd party bir ürün tarafından sağlanan service ile kimlik doğrulaması yapılabilir.
  3. OAuth2 ile: Google / Twitter / Facebook üzerinden kullanıcın bilgilerinin doğrulaması şeklinde.

Önemli not : Eğer veritabanı ile doğrulama yapmayı seçtiyseniz kullanıcı şifresini kesinlikle ve kesinlikle bir şifreleme yöntemi ile şifrelemeyi unutmayınız.

1. UserDetailsService - Kullanıcının şifresine erişimimiz olduğunda#

  • Kullanıcı bilgileriniz veritabanınızda bir tabloda saklandığınızı varsayalım.
id username password
1 user s3cr3t
  • Bu durumda Spring Security‘nin düzgün çalışabilmesini sağlamak için iki adet @Bean‘e ihitiyacımız var.
  1. UserDetailsService
  2. PasswordEncoder

UserDetailsService bean’ini uygun bulduğunuz bir service nesnenizin içine kolayca tanımlayabilirsiniz.

@Bean
public UserDetailsService userDetailsService() {
    return new UserDetailsServiceImpl(); // (1)
}

Ardından UserDetailsService nesneini implement edecek olan UserDetailsServiceImpl nesnesine bakalım.

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    // Autowire the UserRepository
    private final UserRepository userRepository;

    public UserDetailServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // Authenticate a user from the db
    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) {
        // Find user by his username
        
        // If found, feed the user to the createSpringSecurityUser method

        // If not found, throw UsernameNotFoundException exception
    }

    private org.springframework.security.core.userdetails.User createSpringSecurityUser(String loginName, User user) {
        // return a new org.springframework.security.core.userdetails.User which is mapped by your User class
    }
}

Bu implementasyonda önemli olan birkaç durum söz konusu.

  • Dikkat edildiği üzere @Override edilen loadUserByUsername methodu sadece username parametresi almakta. Bu durumda biz de sadece username ile bir arama yapmak durumundayız.
  • Veritabanında bulunan username bilgisi her bir kullanıcı için biricik olmalıdır.

Atacan KULLABCI © 2024