19. Spring Security ile bir web hizmetine erişimi güvenli hale getirme
19.1. Bir web uygulamasında Spring Security'nin yeri
Spring Security'yi bir web uygulaması geliştirme sürecinde ele alalım. Çoğu zaman, bu uygulama aşağıdaki gibi çok katmanlı bir mimari üzerine inşa edilir:
![]() |
- [Spring Security] katmanı, [web] katmanına yalnızca yetkili kullanıcıların erişimine izin verir.
19.2. Spring Security ile ilgili bir eğitim
Aşağıdaki 1'den 3'e kadar olan adımları izleyerek bir Spring kılavuzunu tekrar içe aktaracağız:
![]() |
![]() |
Proje aşağıdaki öğelerden oluşur:
- [templates] klasöründe, projenin HTML sayfaları bulunur;
- [Application]: projenin yürütülebilir sınıfıdır;
- [MvcConfig]: Spring MVC yapılandırma sınıfıdır;
- [WebSecurityConfig]: Spring Security yapılandırma sınıfıdır;
19.2.1. Maven Yapılandırması
[3] projesi bir Maven projesidir. Bağımlılıklarını öğrenmek için [pom.xml] dosyasını inceleyelim:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-securing-web</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- tag::security[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- end::security[] -->
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 10-14. satırlar: proje bir Spring Boot projesidir;
- 17-20. satırlar: [Thymeleaf] çerçevesine bağımlılık;
- 22-25. satırlar: Spring Security çerçevesine bağımlılık;
19.2.2. Thymeleaf görünümleri
![]() |
[home.html] görünümü şu şekildedir:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click <a th:href="@{/hello}">here</a> to see a greeting.
</p>
</body>
</html>
- 12. satır: [th:href="@{/hello}"] özniteliği, <a> etiketinin [href] özniteliğini oluşturacaktır. [@{/hello}] değeri, [<context>/hello] yolunu oluşturacaktır; burada [context], web uygulamasının bağlamıdır;
Oluşturulan HTML kodu şöyledir:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click
<a href="/hello">here</a>
to see a greeting.
</p>
</body>
</html>
[hello.html] görünümü şöyledir:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>
</html>
- 9. satır: [th:inline="text"] özniteliği, <h1> etiketinin metnini oluşturacaktır. Bu metin, değerlendirilmesi gereken bir $ ifadesi içerir. [[${#httpServletRequest.remoteUser}]] öğesi, geçerli HTTP sorgusunun [RemoteUser] özniteliğinin değeridir. Bu, oturum açmış kullanıcının adıdır;
- 10. satır: bir HTML formu. [th:action="@{/logout}"] özniteliği, [form] etiketinin [action] özniteliğini oluşturacaktır. [@{/logout}] değeri, [<context>/logout] yolunu oluşturacaktır; burada [context], web uygulamasının bağlamıdır;
Oluşturulan HTML kodu şöyledir:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello user!</h1>
<form method="post" action="/logout">
<input type="submit" value="Sign Out" />
<input type="hidden" name="_csrf" value="b152e5b9-d1a4-4492-b89d-b733fe521c91" />
</form>
</body>
</html>
- 8. satır: Hello [categorie[2]]! ifadesinin çevirisi;
- 9. satır: @{/logout}'in çevirisi;
- 11. satır: (name özniteliği) adlı gizli bir alan _csrf;
Son görünüm [login.html] şu şekildedir:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<label> User Name : <input type="text" name="username" />
</label>
</div>
<div>
<label> Password: <input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
- 9. satır: [th:if="${param.error}"] özniteliği, <div> etiketinin yalnızca giriş sayfasını görüntüleyen URL'in [error] (http://context/login?error) parametresini içermesi durumunda oluşturulmasını sağlar;
- 10. satır: [th:if="${param.logout}"] özniteliği, <div> etiketinin yalnızca giriş sayfasını görüntüleyen URL etiketinde [logout] parametresi (http://context/login?logout) varsa oluşturulmasını sağlar;
- 11-23. satırlar: bir HTML formu;
- 11. satır: Form, <context> web uygulamasının bağlamı olan URL [<context>/login] adresine gönderilecektir;
- 13. satır: [username] adlı bir giriş alanı;
- 17. satır: [password] adlı bir giriş alanı;
Oluşturulan HTML kodu şöyledir:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div>
You have been logged out.
</div>
<form method="post" action="/login">
<div>
<label>
User Name :
<input type="text" name="username" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
<input type="hidden" name="_csrf" value="ef809b0a-88b4-4db9-bc53-342216b77632" />
</form>
</body>
</html>
28. satırda, Thymeleaf'in [_csrf] adlı gizli bir alan eklediği görülmektedir.
19.2.3. Spring MVC yapılandırması
![]() |
[MvcConfig] sınıfı, Spring MVC çerçevesini yapılandırır:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
- 7. satır: [@Configuration] anotasyonu, [MvcConfig] sınıfını bir yapılandırma sınıfı haline getirir;
- 8. satır: [MvcConfig] sınıfı, [WebMvcConfigurerAdapter] sınıfını genişleterek bazı yöntemlerini yeniden tanımlar;
- 10. satır: üst sınıfın bir yönteminin yeniden tanımlanması;
- satır 11-16: [addViewControllers] yöntemi, URL'leri HTML görünümlerine ilişkilendirmeyi sağlar. Burada aşağıdaki ilişkilendirmeler yapılır:
görünüm | |
/templates/home.html | |
/templates/hello.html | |
/templates/login.html |
[html] uzantısı ve [templates] klasörü, Thymeleaf tarafından kullanılan varsayılan değerlerdir. Bunlar yapılandırma yoluyla değiştirilebilir. [templates] klasörü, projenin Classpath kök dizininde bulunmalıdır:
![]() |
Yukarıdaki [1]'in yanı sıra, [java] ve [resources] klasörlerinin her ikisi de kaynak klasörleridir. Bu, içeriklerinin projenin Classpath kök dizininde yer alacağı anlamına gelir. Dolayısıyla, [2] içinde, [hello] ve [templates] klasörleri Classpath'in kök dizininde yer alacaktır.
19.2.4. Spring Security Yapılandırması
![]() |
[WebSecurityConfig] sınıfı, Spring Security çerçevesini yapılandırır:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
- 9. satır: [@Configuration] anotasyonu, [WebSecurityConfig] sınıfını bir yapılandırma sınıfı haline getirir;
- 10. satır: [@EnableWebSecurity] anotasyonu, [WebSecurityConfig] sınıfını bir Spring Security yapılandırma sınıfı haline getirir;
- 11. satır: [WebSecurity] sınıfı, [WebSecurityConfigurerAdapter] sınıfını genişleterek bazı yöntemlerini yeniden tanımlar;
- 12. satır: üst sınıfın bir yöntemi yeniden tanımlanmıştır;
- satır 13-16: [configure(HttpSecurity http)] yöntemi, uygulamanın çeşitli URL'lerine erişim haklarını tanımlamak üzere yeniden tanımlanmıştır;
- 14. satır: [http.authorizeRequests()] yöntemi, URL'leri erişim haklarıyla ilişkilendirmeyi sağlar. Burada aşağıdaki ilişkilendirmeler yapılır:
kural | kod | |
kimlik doğrulaması yapılmadan erişim | | |
sadece kimlik doğrulaması ile erişim |
- 15. satır: kimlik doğrulama yöntemini tanımlar. Kimlik doğrulama, herkesin erişebileceği bir URL [/login] formu aracılığıyla yapılır [http.formLogin().loginPage("/login").permitAll()]. Oturum kapatma (logout) işlemi de herkes tarafından kullanılabilir;
- 19-21. satırlar: kullanıcıları yöneten [configure(AuthenticationManagerBuilder auth)] yöntemini yeniden tanımlar;
- 20. satır: Kimlik doğrulama, "sabit" olarak tanımlanmış kullanıcılarla yapılır ([auth.inMemoryAuthentication()]). Burada bir kullanıcı, [user] kullanıcı adı, [password] şifre ve [USER] rolü ile tanımlanmıştır. Aynı role sahip kullanıcılara aynı haklar verilebilir;
19.2.5. Çalıştırılabilir sınıf
![]() |
[Application] sınıfı aşağıdaki gibidir:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
- 8. satır: [@EnableAutoConfiguration] anotasyonu, Spring Boot'tan (3. satır) geliştiricinin açıkça yapmadığı yapılandırmayı gerçekleştirmesini ister;
- 9. satır: [Application] sınıfını bir Spring yapılandırma sınıfı haline getirir;
- 10. satır: Spring bileşenlerini aramak üzere [Application] sınıfının bulunduğu klasörün taranmasını ister. [MvcConfig] ve [WebSecurityConfig] sınıfları, [@Configuration] anotasyonuna sahip oldukları için bu şekilde tespit edilecektir;
- 13. satır: yürütülebilir sınıfın [main] yöntemi;
- 14. satır: [SpringApplication.run] statik yöntemi, [Application] yapılandırma sınıfını parametre olarak alarak yürütülür. Bu süreci daha önce de görmüştük ve projenin Maven bağımlılıklarında bulunan gömülü Tomcat sunucusunun başlatılacağını ve projenin bu sunucuya dağıtılacağını biliyoruz. Dört adet URL'in [/, /home, /login, /hello] tarafından yönetildiğini ve bazılarının erişim haklarıyla korunduğunu görmüştük.
19.2.6. Uygulama Testleri
Öncelikle, kabul edilen dört URL dosyasından biri olan URL [/] dosyasını talep edelim. Bu dosya, [/templates/home.html] görünümüne bağlıdır:
![]() |
Talep edilen URL, [/] herkes tarafından erişilebilir. Bu yüzden onu elde ettik. [here] bağlantısı şöyledir:
Bağlantıya tıklandığında URL ve [/hello] dosyaları istenecektir. Bu dosya korumalıdır:
kural | kod | |
kimlik doğrulaması yapılmadan erişim | | |
Yalnızca kimlik doğrulaması ile erişim |
Bunu elde etmek için kimlik doğrulaması yapılmalıdır. Spring Security, istemci tarayıcısını kimlik doğrulama sayfasına yönlendirecektir. Görülen yapılandırmaya göre, bu sayfa URL [/login]'tir. Bu sayfaya herkes erişebilir:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
Böylece [1] adresini elde ediyoruz:
![]() |
Elde edilen sayfanın kaynak kodu şöyledir:
- 7. satırda, orijinal [login.html] sayfasında bulunmayan gizli bir alan ortaya çıkıyor. Bunu Thymeleaf eklemiştir. CSRF (Cross Site Request Forgery) olarak adlandırılan bu kod, bir güvenlik açığını ortadan kaldırmayı amaçlamaktadır. Bu jetonun, kimlik doğrulamanın kabul edilmesi için Spring Security’ye geri gönderilmesi gerekir;
Hatırlayacağımız üzere, Spring Security tarafından yalnızca user/password kullanıcı adı ve şifresi tanınır. [2]'e başka bir şey girersek, [3]'te bir hata mesajı içeren aynı sayfayı alırız. Spring Security, tarayıcıyı URL [http://localhost:8080/login?error] adresine yönlendirdi. [error] parametresinin varlığı, şu etiketin görüntülenmesini tetikledi:
<div th:if="${param.error}">Invalid username and password.</div>
Şimdi, beklenen user/password değerlerini [4] olarak girelim:
![]() |
- [4]'te kimlik doğrulaması yapıyoruz;
- [5] adresinde, Spring Security bizi URL [/hello] adresine yönlendirir, çünkü giriş sayfasına yönlendirildiğimizde talep ettiğimiz adres URL idi. Kullanıcının kimliği, [hello.html]'teki şu satırda görüntülenmiştir:
[5] sayfası aşağıdaki formu görüntülüyor:
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
[Sign Out] düğmesine tıklandığında, URL ve [/logout] sayfalarında bir POST işlemi gerçekleştirilecektir. Bu dosya, URL ve [/login] dosyaları gibi herkes tarafından erişilebilir:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
URL / views derlememizde, URL ve [/logout] için herhangi bir tanımlama yapmadık. Ne olacak? Deneyelim:
![]() |
- [6]'te, [Sign Out] düğmesine tıklıyoruz;
- [7]'te, URL ve [http://localhost:8080/login?logout]'e yönlendirildiğimizi görüyoruz. Bu yönlendirmeyi Spring Security talep etmiştir. URL içinde [logout] parametresinin bulunması, görünümde şu satırın görüntülenmesine neden oldu:
<div th:if="${param.logout}">You have been logged out.</div>
19.2.7. Sonuç
Önceki örnekte, önce web uygulamasını yazıp ardından güvenliğini sağlayabilirdik. Spring Security müdahaleci değildir. Zaten yazılmış bir web uygulamasının güvenliğini de sağlayabiliriz. Ayrıca, şu noktaları da keşfettik:
- bir kimlik doğrulama sayfası tanımlamak mümkündür;
- kimlik doğrulama işlemi, Spring Security tarafından verilen CSRF jetonuyla birlikte yapılmalıdır;
- kimlik doğrulama başarısız olursa, URL içinde bir error parametresi ile birlikte kimlik doğrulama sayfasına yönlendiriliriz;
- kimlik doğrulama başarılı olursa, kimlik doğrulama işlemi gerçekleştiğinde istenen sayfaya yönlendirilirsiniz. Ara sayfa üzerinden geçmeden doğrudan kimlik doğrulama sayfasına erişilmeye çalışılırsa, Spring Security bizi URL ve [/]'e yönlendirir (bu durum ele alınmamıştır);
- POST ile URL [/logout] sayfasını talep ederek oturumdan çıkılır. Spring Security, URL'te logout parametresiyle bizi kimlik doğrulama sayfasına yönlendirir;
Tüm bu sonuçlar, Spring Security'nin varsayılan davranışlarına dayanmaktadır. Bu davranışlar, [WebSecurityConfigurerAdapter] sınıfındaki belirli yöntemleri yeniden tanımlayarak yapılandırma yoluyla değiştirilebilir.
Önceki öğretici, bundan sonra pek işimize yaramayacak. Aslında şunları kullanacağız:
- kullanıcıları, şifrelerini ve rollerini depolamak için bir veritabanı;
- HTTP başlık tabanlı kimlik doğrulama;
Burada yapmak istediğimiz şey için oldukça az sayıda öğretici bulunmaktadır. Önerilecek çözüm, çeşitli kaynaklardan derlenen kodların bir birleşimidir.














