Spring Security
- Spring security ile ilk tanışacaklar için daha önce hazırlamış olduğum
Spring Fundamentals
veSpring 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;
- Authentication (Kimlik Doğrulaması)
- Authorization (Yetkilendirme)
- 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
veRol
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üphaneleriniDispatcherServlet
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. BuFilter
ı servlet imizin önüne konuçlandırabiliriz.
Ö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;
- Kullanıcı için sizin login sayfanıza ulaşıp ulaşamaması için oluşturulan
LoginFilter
- Ardından kimlik doğrulaması için
AuthenticationFilter
- Ardından da yetki doğrulamaları için
AuhtorizationFilter
- 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ıylaSpring 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 projeyeSpring 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 taneServletFilter
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;
- Konfigürasyon sınıfına
@EnableWebSecurity
anotasyonu eklemek. - Konfigürasyon sınıfının
WebSecurityConfigurerAdapter
sı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)
}
}
-
/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. -
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.
-
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. -
Aynı şekilde çıkış sayfası için de herkesin erişimi olacak.
-
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;
- Veritabanı ile: Kullanıcının şifrelenmiş bilgilerini (Username / Password) alıp veritabanındaki bilgileri ile karşılaştırmak.
- 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.
- 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.
- UserDetailsService
- 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.