Merhabalar arkadaşlar.

Önceki yazımızda Spring Boot ve Spring Security ile Restful web servisi inmemory user ile koruma altına almayı görmüştük. Bu yazıda ise databese’de tanımlı kullanıcılar yetkilendirme işlemini örnekleyeceğiz.

Bu yazıdaki uygulamanın kodlarına https://github.com/ilkgunel/SpringSecurityWithSpringBoot adresinden erişebilirsiniz. Ayrıca kullandığımız veritabanı için de bir SQL dosyası ekledim, direk onu import edebilsiniz.

Öncelikle veritabanına bakalım. Kullanıcılarımızı tutmak için bir Member tablomuz var. Bu tablo üzerinde Spring Security kullanacağımız alanlar password, memberUserName ve enabled alanlarıdır. memberUserName alanı tekrar etmeyen alan olarak düşüneceğiz bu yazı boyunca. password alanı ise BCrypt ile hashlenip saklanan bir alan. enable alanı ise user’ın aktif mi pasif mi durumda olduğunu bildiren alandır.

Kullanıcıların rol bazı yönetimini yapmak içinde userroles tablosunda ilgili kullanıcının rolünü tutuyoruz. Member tablosunda tekrar etmeyen alanımız olan memberUserName burada da belirleyici alan. Role kolonunda ilgili user’ın rolünü tutuyoruz.

Şimdi Java sınıflarımıza bakalım.

AppConfig.java

package com.ilkaygunel.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
public class AppConfig {
	@Bean(name = "dataSource")
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/SpringSecurityWithSpringBoot");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("");
        return driverManagerDataSource;
    }	
}

AppConfig sınıfımız içerisinde @Bean notasyonu ile dataSource isminde bir bean tanımı yapıyoruz. dataSource() metodu ile de bize veritabanı bağlantısı sunacak bir driverManagerDataSource nesnesi elde ediyoruz. Veritabanına bağlantı için yapılması gereken tanımlar driverManagerDataSource nesnesi üzerinde yapılabiliyor. Örneğin setDriverClassName() metodu ile kullanacağımız veritabanının driver’ını bildiriyoruz ve biz MySQL kullanacağız.

application.properties

server.contextPath=/SpringSecurityWithSpringBoot
server.port=8080
usersByUsernameQuery=select memberUserName,password,enabled from Member where memberUserName=?
authoritiesByUsernameQuery=select memberUserName,role from userroles where memberUserName=?

application.properties dosyamız içerisine gelen kullanıcı adı bilgisine göre veritabanından user çekecek usersByUsernameQuery ve eğer user var ise onun yetkilendirmesine bakmak için sorgu atacak authoritiesByUsernameQuery property’lerini ekledik.

WebSecurityConfig.java

package com.ilkaygunel.security;

import java.util.Arrays;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@Configuration
@PropertySource("classpath:application.properties")
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private DataSource dataSource;
	
	@Autowired
    private Environment env;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues()).and().csrf()
				.disable().authorizeRequests().antMatchers("/memberList").access("hasRole('ROLE_ADMIN')")
				.and().httpBasic();
	}

	@Bean
	CorsConfigurationSource corsConfigurationSource() {
		CorsConfiguration configuration = new CorsConfiguration();
		configuration.setAllowedOrigins(Arrays.asList("*"));
		configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", configuration);
		return source;
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(env.getProperty("usersByUsernameQuery"))
		.authoritiesByUsernameQuery(env.getProperty("authoritiesByUsernameQuery"))
		.passwordEncoder(new BCryptPasswordEncoder());
	}
	
	@Override
    public void configure(WebSecurity web) throws Exception {
        web.debug(true);
    }

//	@Override
//	public void configure(AuthenticationManagerBuilder auth) throws Exception {
//		auth.inMemoryAuthentication().withUser("adminUser").password("adminUserPassword").roles("ADMIN");
//		auth.inMemoryAuthentication().withUser("standartUser").password("standartUserPassword").roles("USER");
//	}
}

Önceki yazımızda inMemoryUser ile doğrulama ve yetkilendirme yaptığımız WebSecurityConfig sınıfımızı şimdi database’de tanımlı user ile yapacak şekilde değiştiriyoruz.

Sınıfımızın başında @Autowired notasyonu ile az önce AppConfig sınıfı içerisinde oluşturduğumuz dataSource bean’ini sınıfımıza inject ediyoruz.

Yine @Autowired notasyonu bir Environment nesnesini inject ediyoruz. Environment nesnesi ile application.properties dosyası içerisindeki property’lerimizi kolaylıkle çekebiliyoruz. Bunu yapabilmek için bir de sınıfın @PropertySource notasyonu ile işaretli olması yeterli.

configure(AuthenticationManagerBuilder auth) metodu içinde

  • auth.jdbcAuthentication() ile dorulama&yetkilendirmenin veritabanı aracılığı ile yapılacağını söylüyoruz.
  • .dataSource(dataSource) ile az önce AppConfig içerisinde oluşturup sınıfımıza inject ettiğimiz dataSource nesnesini metoda vererek veritabanı bağlantı bilgilerini veriyoruz.
  • .usersByUsernameQuery() metodu ile giriş yapan kullanıcının veritabanı üzerinde tanımlı olup olmadığı kontrolünü yapıyoruz. .usersByUsernameQuery() metoduna verilen SQL sorgusu gelen username bilgisi ile memberUserName alanını kullanarak member tablosu üzerinde sorgu atıyor.
  • .authoritiesByUsernameQuery() metodu ise eğer kullanıcı mevcut ise ve parolayı doğru girmiş ise yetkisinin doğru olup olmadığına bakıyor.
  • .passwordEncoder() metodu da veritabanında hash’li olarak tutulan kullanıcı parolasının neye göre karşılaştıracağını bildirir. Yani biz veritabanında BCrypt ile hash’lenmiş bir passowrd tutuyoruz ve Spring Security’e de gelen parola bilgisini BCryptPasswordEncoder sınıfı aracılığı ile encode et ve öyle karşılaştır demiş oluyoruz.

Önceki yazımızdan farklı olarak bu yazıda bir de corsConfigurationSource() metodu ve configure(HttpSecurity http) metodu içinde cors için tanımlar yer alıyor. Açılımı Cross-Origin Resource Sharing CORS kaynaklarımıza erişim için domain bazlı tanımlar yapmayı sağlayan bir mekanizmadır. CORS mekanizmasını sunucularda ön tanımlı olarak kapalı gelir, bunun sebebi de kötüye kullanımları önlemektir.

Şimdi önceki yazıda olduğu gibi Application sınıfına sağ tıklayıp Run As -> Java Application diyerek Spring Boot projesini ayağa kaldıralım ve Postman üzerinden aynı requestleri atalım.

İlk olarak yetkisi olmayan kullanıcının erişememesi testini yapalım.

ilkay.gunel username’ini ve TEST1234 parolasını headerda Basic Authentication kullanarak web servise isteğimizi atıyoruz. ilkay.gunel kullanıcısı ROLE_USER rolüne sahip bir kullanıcı. Web servis de bize kod içerisinde sadece ROLE_ADMIN rolüne sahip kullanıcıların buraya erişebileceği tanımlandığı için 403-Access Denied hatası ile dönüyor.

Şimdi ROLE_ADMIN rolüne sahip ertan.sahin kullanıcısı ve TEST1234 parolası ile istek gönderelim (username&password değiştirdikten sonra sağ taraftaki Update Request butonuna tıklamayı unutmayalım). İsteğe cevap olarak web servisten datalar dönüyor.

Bir de son olarak hiç tanımlı olmayan user bilgisi ile test edelim. Eğer hiç tanımlı olmayan bir kullanıcı ile de gidersek 401 - Bad credentials mesajı ile geri çevriliyoruz.

Bu yazıda anlatacaklarım bu kadar arkadaşlar. İki yazı ile Spring Boot projesi içerisinde Spring Security kullanımını incelemiş olduk.

Başka yazıda görüşene kadar sağlıcakla kalın.

Selam ve Sevgilerimle