REST API için JWT ile Bahar Güvenliği
Yayınlanan: 2022-03-11Spring, Java ekosisteminde güvenilir bir çerçeve olarak kabul edilir ve yaygın olarak kullanılır. Spring'den bir çerçeve olarak bahsetmek artık geçerli değil, çünkü daha çok çeşitli çerçeveleri kapsayan bir şemsiye terim. Bu çerçevelerden biri, güçlü ve özelleştirilebilir bir kimlik doğrulama ve yetkilendirme çerçevesi olan Spring Security'dir. Yay tabanlı uygulamaların güvenliğini sağlamak için fiili standart olarak kabul edilir.
Popülerliğine rağmen, tek sayfalık uygulamalar söz konusu olduğunda, yapılandırmanın basit ve kolay olmadığını itiraf etmeliyim. Bunun nedeninin, web sayfası oluşturmanın sunucu tarafında gerçekleştiği ve iletişimin oturum tabanlı olduğu MVC uygulama odaklı bir çerçeve olarak başlamasından şüpheleniyorum.
Arka uç Java ve Spring'i temel alıyorsa, kimlik doğrulama/yetkilendirme için Spring Security'yi kullanmak ve onu durumsuz iletişim için yapılandırmak mantıklıdır. Bunun nasıl yapıldığını açıklayan birçok makale olmasına rağmen, benim için ilk kez kurmak hala sinir bozucuydu ve birden fazla kaynaktan bilgi okuyup özetlemek zorunda kaldım. Bu nedenle, yapılandırma sürecinde karşılaşabileceğiniz tüm gerekli ince ayrıntıları ve zaafları özetlemeye çalışacağım bu makaleyi yazmaya karar verdim.
Terminolojiyi Tanımlamak
Teknik ayrıntılara girmeden önce, hepimizin aynı dili konuştuğumuzdan emin olmak için Spring Security bağlamında kullanılan terminolojiyi açıkça tanımlamak istiyorum.
Bunlar, ele almamız gereken terimlerdir:
- Kimlik doğrulama, sağlanan kimlik bilgilerine dayalı olarak bir kullanıcının kimliğini doğrulama sürecini ifade eder. Yaygın bir örnek, bir web sitesinde oturum açtığınızda bir kullanıcı adı ve şifre girmektir. Sen kimsin sorusuna cevap olarak düşünebilirsiniz. .
- Yetkilendirme , kullanıcının kimliğinin başarıyla doğrulandığını varsayarak, bir kullanıcının belirli bir eylemi gerçekleştirmek veya belirli verileri okumak için uygun izne sahip olup olmadığını belirleme sürecini ifade eder. Bunu bir kullanıcı yapabilir mi/okuyabilir mi sorusuna bir cevap olarak düşünebilirsiniz. .
- İlke , şu anda kimliği doğrulanmış kullanıcıyı ifade eder.
- Verilen yetki , kimliği doğrulanmış kullanıcının iznini ifade eder.
- Rol , kimliği doğrulanmış kullanıcının bir grup izin anlamına gelir.
Temel Yay Uygulaması Oluşturma
Spring Security çerçevesinin konfigürasyonuna geçmeden önce, temel bir Spring web uygulaması oluşturalım. Bunun için bir Spring Initializr kullanabilir ve bir şablon proje oluşturabiliriz. Basit bir web uygulaması için yalnızca Spring web çerçevesi bağımlılığı yeterlidir:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
Projeyi oluşturduktan sonra, ona basit bir REST denetleyicisi ekleyebiliriz:
@RestController @RequestMapping("hello") public class HelloRestController { @GetMapping("user") public String helloUser() { return "Hello User"; } @GetMapping("admin") public String helloAdmin() { return "Hello Admin"; } }
Bundan sonra, projeyi derleyip çalıştırırsak, web tarayıcısında aşağıdaki URL'lere erişebiliriz:
-
http://localhost:8080/hello/user
Hello User
dizesini döndürür. -
http://localhost:8080/hello/admin
Hello Admin
dizesini döndürür.
Artık Spring Security frameworkünü projemize ekleyebiliriz ve bunu pom.xml
dosyamıza aşağıdaki bağımlılığı ekleyerek yapabiliriz:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies>
Diğer Spring çerçeve bağımlılıklarını eklemek, biz ilgili konfigürasyonu sağlayana kadar normalde bir uygulama üzerinde hemen bir etkiye sahip değildir, ancak Spring Security, anında bir etkiye sahip olması bakımından farklıdır ve bu genellikle yeni kullanıcıları şaşırtmaktadır. Ekledikten sonra, projeyi yeniden oluşturup çalıştırırsak ve sonucu görmek yerine yukarıda belirtilen URL'lerden birine erişmeye çalışırsak, http://localhost:8080/login
adresine yönlendirileceğiz. Spring Security çerçevesi, tüm URL'ler için kutudan çıkar çıkmaz kimlik doğrulama gerektirdiğinden, bu varsayılan davranıştır.
Kimlik doğrulamasını geçmek için varsayılan user
adını kullanabilir ve konsolumuzda otomatik olarak oluşturulmuş bir şifre bulabiliriz:
Using generated security password: 1fc15145-dfee-4bec-a009-e32ca21c77ce
Uygulamayı her yeniden çalıştırdığımızda şifrenin değiştiğini lütfen unutmayın. Bu davranışı değiştirmek ve şifreyi statik yapmak istiyorsak, application.properties
dosyamıza aşağıdaki konfigürasyonu ekleyebiliriz:
spring.security.user.password=Test12345_
Şimdi, giriş formuna kimlik bilgilerini girersek, tekrar URL'mize yönlendirileceğiz ve doğru sonucu göreceğiz. Lütfen kullanıma hazır kimlik doğrulama işleminin oturum tabanlı olduğunu ve oturumu kapatmak istiyorsak şu URL'ye erişebileceğimizi unutmayın: http://localhost:8080/logout
Bu kullanıma hazır davranış, oturum tabanlı kimlik doğrulamaya sahip olduğumuz klasik MVC web uygulamaları için yararlı olabilir, ancak tek sayfalık uygulamalar söz konusu olduğunda, çoğu kullanım durumunda istemci tarafımız olduğundan genellikle kullanışlı değildir. işleme ve JWT tabanlı durum bilgisi olmayan kimlik doğrulama. Bu durumda, makalenin geri kalanında yapacağımız Spring Security çerçevesini yoğun bir şekilde özelleştirmemiz gerekecek.
Örnek olarak, klasik bir kitapçı web uygulaması uygulayacağız ve yazarlar ve kitaplar oluşturmak için CRUD API'leri ve kullanıcı yönetimi ve kimlik doğrulaması için API'ler sağlayacak bir arka uç oluşturacağız.
Spring Security Mimarisine Genel Bakış
Yapılandırmayı özelleştirmeye başlamadan önce, Spring Security kimlik doğrulamasının perde arkasında nasıl çalıştığını tartışalım.
Aşağıdaki şema, akışı sunar ve kimlik doğrulama isteklerinin nasıl işlendiğini gösterir:
Bahar Güvenlik Mimarisi

Şimdi bu diyagramı bileşenlerine ayıralım ve her birini ayrı ayrı tartışalım.
Yaylı Güvenlik Filtreleri Zinciri
Spring Security çerçevesini uygulamanıza eklediğinizde, gelen tüm istekleri engelleyen bir filtre zincirini otomatik olarak kaydeder. Bu zincir çeşitli filtrelerden oluşur ve her biri belirli bir kullanım durumunu ele alır.
Örneğin:
- Yapılandırmaya göre, istenen URL'nin herkese açık olup olmadığını kontrol edin.
- Oturuma dayalı kimlik doğrulama durumunda, kullanıcının geçerli oturumda zaten kimliğinin doğrulanıp doğrulanmadığını kontrol edin.
- Kullanıcının istenen eylemi gerçekleştirme yetkisi olup olmadığını kontrol edin, vb.
Bahsetmek istediğim önemli bir detay da Spring Security filtrelerinin en düşük sıra ile kaydedildiği ve ilk çağrılan filtreler olduğudur. Bazı kullanım durumlarında, önlerine özel filtrenizi koymak istiyorsanız, siparişlerine dolgu eklemeniz gerekir. Bu, aşağıdaki yapılandırma ile yapılabilir:
spring.security.filter.order=10
Bu konfigürasyonu application.properties
dosyamıza eklediğimizde, Spring Security filtrelerinin önünde 10 özel filtre için yerimiz olacak.
Kimlik Doğrulama Yöneticisi
AuthenticationManager
birden fazla sağlayıcıyı kaydedebileceğiniz bir koordinatör olarak düşünebilirsiniz ve istek türüne göre doğru sağlayıcıya bir kimlik doğrulama isteği gönderir.
Kimlik Doğrulama Sağlayıcı
AuthenticationProvider
, belirli kimlik doğrulama türlerini işler. Arayüzü yalnızca iki işlevi ortaya çıkarır:
-
authenticate
doğrulama, istekle kimlik doğrulaması gerçekleştirir. - bu sağlayıcının belirtilen kimlik doğrulama türünü destekleyip
supports
kontrol eder.
Örnek projemizde kullandığımız arabirimin önemli bir uygulaması, bir DaoAuthenticationProvider
kullanıcı ayrıntılarını alan UserDetailsService
.
KullanıcıAyrıntılarıHizmet
UserDetailsService
, Spring belgelerinde kullanıcıya özel verileri yükleyen bir çekirdek arabirim olarak tanımlanır.
Çoğu kullanım durumunda, kimlik doğrulama sağlayıcıları, bir veritabanından kimlik bilgilerine dayalı olarak kullanıcı kimlik bilgilerini alır ve ardından doğrulama gerçekleştirir. Bu kullanım durumu çok yaygın olduğu için, Spring geliştiricileri onu tek işlevi ortaya çıkaran ayrı bir arayüz olarak çıkarmaya karar verdiler:
-
loadUserByUsername
, kullanıcı adını parametre olarak kabul eder ve kullanıcı kimliği nesnesini döndürür.
Spring Security ile JWT Kullanarak Kimlik Doğrulama
Spring Security çerçevesinin içindekileri tartıştıktan sonra, onu bir JWT belirteci ile durumsuz kimlik doğrulaması için yapılandıralım.
Spring Security'yi özelleştirmek için, sınıf @EnableWebSecurity
açıklamalı bir yapılandırma sınıfına ihtiyacımız var. Ayrıca, özelleştirme sürecini basitleştirmek için çerçeve bir WebSecurityConfigurerAdapter
sınıfını ortaya çıkarır. Bu bağdaştırıcıyı genişleteceğiz ve her iki işlevini de şu şekilde geçersiz kılacağız:
- Doğru sağlayıcı ile kimlik doğrulama yöneticisini yapılandırın
- Web güvenliğini yapılandırın (genel URL'ler, özel URL'ler, yetkilendirme vb.)
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // TODO configure authentication manager } @Override protected void configure(HttpSecurity http) throws Exception { // TODO configure web security } }
Örnek uygulamamızda, kullanıcı kimliklerini bir MongoDB veritabanında, users
koleksiyonunda saklıyoruz. Bu kimlikler, User
varlığı tarafından eşlenir ve bunların CRUD işlemleri, UserRepo
Spring Data deposu tarafından tanımlanır.
Şimdi, kimlik doğrulama isteğini kabul ettiğimizde, sağlanan kimlik bilgilerini kullanarak veritabanından doğru kimliği almamız ve ardından doğrulamamız gerekiyor. Bunun için aşağıdaki gibi tanımlanan UserDetailsService
arabiriminin uygulanmasına ihtiyacımız var:
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
Burada, UserDetails
arabirimini uygulayan nesneyi döndürmenin gerekli olduğunu ve User
varlığımızın bunu uyguladığını görebiliriz (uygulama ayrıntıları için lütfen örnek projenin deposuna bakın). Yalnızca tek işlevli prototipi ortaya çıkardığı gerçeğini göz önünde bulundurarak, onu işlevsel bir arayüz olarak ele alabilir ve bir lambda ifadesi olarak uygulama sağlayabiliriz.
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserRepo userRepo; public SecurityConfig(UserRepo userRepo) { this.userRepo = userRepo; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(username -> userRepo .findByUsername(username) .orElseThrow( () -> new UsernameNotFoundException( format("User: %s, not found", username) ) )); } // Details omitted for brevity }
Burada, auth.userDetailsService
işlev çağrısı, UserDetailsService
arabirimi uygulamamızı kullanarak DaoAuthenticationProvider
örneğini başlatacak ve onu kimlik doğrulama yöneticisine kaydedecektir.
Kimlik doğrulama sağlayıcısıyla birlikte, kimlik doğrulaması için kullanılacak doğru parola kodlama şemasına sahip bir kimlik doğrulama yöneticisi yapılandırmamız gerekiyor. Bunun için, PasswordEncoder
arabiriminin tercih edilen uygulamasını bir bean olarak ortaya çıkarmamız gerekiyor.
Örnek projemizde bcrypt password hashing algoritmasını kullanacağız.
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserRepo userRepo; public SecurityConfig(UserRepo userRepo) { this.userRepo = userRepo; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(username -> userRepo .findByUsername(username) .orElseThrow( () -> new UsernameNotFoundException( format("User: %s, not found", username) ) )); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // Details omitted for brevity }
Kimlik doğrulama yöneticisini yapılandırdıktan sonra, şimdi web güvenliğini yapılandırmamız gerekiyor. Bir REST API uyguluyoruz ve bir JWT belirteci ile durum bilgisi olmayan kimlik doğrulamaya ihtiyacımız var; bu nedenle, aşağıdaki seçenekleri ayarlamamız gerekiyor:
- CORS'u etkinleştirin ve CSRF'yi devre dışı bırakın.
- Oturum yönetimini durumsuz olarak ayarlayın.
- Yetkisiz istekler istisna işleyicisini ayarlayın.
- Uç noktalarda izinleri ayarlayın.
- JWT belirteç filtresi ekleyin.
Bu yapılandırma aşağıdaki gibi uygulanır:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserRepo userRepo; private final JwtTokenFilter jwtTokenFilter; public SecurityConfig(UserRepo userRepo, JwtTokenFilter jwtTokenFilter) { this.userRepo = userRepo; this.jwtTokenFilter = jwtTokenFilter; } // Details omitted for brevity @Override protected void configure(HttpSecurity http) throws Exception { // Enable CORS and disable CSRF http = http.cors().and().csrf().disable(); // Set session management to stateless http = http .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and(); // Set unauthorized requests exception handler http = http .exceptionHandling() .authenticationEntryPoint( (request, response, ex) -> { response.sendError( HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage() ); } ) .and(); // Set permissions on endpoints http.authorizeRequests() // Our public endpoints .antMatchers("/api/public/**").permitAll() .antMatchers(HttpMethod.GET, "/api/author/**").permitAll() .antMatchers(HttpMethod.POST, "/api/author/search").permitAll() .antMatchers(HttpMethod.GET, "/api/book/**").permitAll() .antMatchers(HttpMethod.POST, "/api/book/search").permitAll() // Our private endpoints .anyRequest().authenticated(); // Add JWT token filter http.addFilterBefore( jwtTokenFilter, UsernamePasswordAuthenticationFilter.class ); } // Used by spring security if CORS is enabled. @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
Lütfen JwtTokenFilter
Spring Security dahili UsernamePasswordAuthenticationFilter
öğesinden önce eklediğimizi unutmayın. Bunu yapıyoruz çünkü kimlik doğrulama/yetkilendirme gerçekleştirmek için bu noktada kullanıcı kimliğine erişmemiz gerekiyor ve çıkarma işlemi sağlanan JWT belirtecine dayalı olarak JWT belirteç filtresinin içinde gerçekleşiyor. Bu, aşağıdaki şekilde uygulanır:
@Component public class JwtTokenFilter extends OncePerRequestFilter { private final JwtTokenUtil jwtTokenUtil; private final UserRepo userRepo; public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, UserRepo userRepo) { this.jwtTokenUtil = jwtTokenUtil; this.userRepo = userRepo; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // Get authorization header and validate final String header = request.getHeader(HttpHeaders.AUTHORIZATION); if (isEmpty(header) || !header.startsWith("Bearer ")) { chain.doFilter(request, response); return; } // Get jwt token and validate final String token = header.split(" ")[1].trim(); if (!jwtTokenUtil.validate(token)) { chain.doFilter(request, response); return; } // Get user identity and set it on the spring security context UserDetails userDetails = userRepo .findByUsername(jwtTokenUtil.getUsername(token)) .orElse(null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails == null ? List.of() : userDetails.getAuthorities() ); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request) ); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } }
Oturum açma API işlevimizi uygulamadan önce bir adımla daha ilgilenmemiz gerekiyor - kimlik doğrulama yöneticisine erişmemiz gerekiyor. Varsayılan olarak, herkese açık değildir ve onu yapılandırma sınıfımızda açıkça bir fasulye olarak göstermemiz gerekir.

Bu şöyle yapılabilir:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // Details omitted for brevity @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
Ve şimdi, oturum açma API işlevimizi uygulamaya hazırız:
@Api(tags = "Authentication") @RestController @RequestMapping(path = "api/public") public class AuthApi { private final AuthenticationManager authenticationManager; private final JwtTokenUtil jwtTokenUtil; private final UserViewMapper userViewMapper; public AuthApi(AuthenticationManager authenticationManager, JwtTokenUtil jwtTokenUtil, UserViewMapper userViewMapper) { this.authenticationManager = authenticationManager; this.jwtTokenUtil = jwtTokenUtil; this.userViewMapper = userViewMapper; } @PostMapping("login") public ResponseEntity<UserView> login(@RequestBody @Valid AuthRequest request) { try { Authentication authenticate = authenticationManager .authenticate( new UsernamePasswordAuthenticationToken( request.getUsername(), request.getPassword() ) ); User user = (User) authenticate.getPrincipal(); return ResponseEntity.ok() .header( HttpHeaders.AUTHORIZATION, jwtTokenUtil.generateAccessToken(user) ) .body(userViewMapper.toUserView(user)); } catch (BadCredentialsException ex) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } }
Burada, kimlik doğrulama yöneticisini kullanarak sağlanan kimlik bilgilerini doğrularız ve başarı durumunda, JWT belirtecini oluşturur ve yanıt gövdesindeki kullanıcı kimlik bilgileriyle birlikte bir yanıt başlığı olarak döndürürüz.
Spring Security ile yetkilendirme
Önceki bölümde, bir kimlik doğrulama süreci kurduk ve genel/özel URL'leri yapılandırdık. Bu, basit uygulamalar için yeterli olabilir, ancak çoğu gerçek dünya kullanım durumu için, kullanıcılarımız için her zaman rol tabanlı erişim politikalarına ihtiyaç duyarız. Bu bölümde, bu sorunu ele alacağız ve Spring Security çerçevesini kullanarak rol tabanlı bir yetkilendirme şeması oluşturacağız.
Örnek uygulamamızda aşağıdaki üç rolü tanımladık:
-
USER_ADMIN
, uygulama kullanıcılarını yönetmemize izin verir. -
AUTHOR_ADMIN
, yazarları yönetmemize izin verir. -
BOOK_ADMIN
, kitapları yönetmemizi sağlar.
Şimdi bunları ilgili URL'lere uygulamamız gerekiyor:
-
api/public
herkese açık. -
api/admin/user
,USER_ADMIN
rolüne sahip kullanıcılara erişebilir. -
api/author
,AUTHOR_ADMIN
rolüne sahip kullanıcılara erişebilir. -
api/book
,BOOK_ADMIN
rolüne sahip kullanıcılara erişebilir.
Spring Security çerçevesi, yetkilendirme şemasını kurmak için bize iki seçenek sunar:
- URL tabanlı yapılandırma
- Açıklama tabanlı yapılandırma
İlk olarak, URL tabanlı yapılandırmanın nasıl çalıştığını görelim. Web güvenlik yapılandırmasına aşağıdaki gibi uygulanabilir:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // Details omitted for brevity @Override protected void configure(HttpSecurity http) throws Exception { // Enable CORS and disable CSRF http = http.cors().and().csrf().disable(); // Set session management to stateless http = http .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and(); // Set unauthorized requests exception handler http = http .exceptionHandling() .authenticationEntryPoint( (request, response, ex) -> { response.sendError( HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage() ); } ) .and(); // Set permissions on endpoints http.authorizeRequests() // Our public endpoints .antMatchers("/api/public/**").permitAll() .antMatchers(HttpMethod.GET, "/api/author/**").permitAll() .antMatchers(HttpMethod.POST, "/api/author/search").permitAll() .antMatchers(HttpMethod.GET, "/api/book/**").permitAll() .antMatchers(HttpMethod.POST, "/api/book/search").permitAll() // Our private endpoints .antMatchers("/api/admin/user/**").hasRole(Role.USER_ADMIN) .antMatchers("/api/author/**").hasRole(Role.AUTHOR_ADMIN) .antMatchers("/api/book/**").hasRole(Role.BOOK_ADMIN) .anyRequest().authenticated(); // Add JWT token filter http.addFilterBefore( jwtTokenFilter, UsernamePasswordAuthenticationFilter.class ); } // Details omitted for brevity }
Gördüğünüz gibi, bu yaklaşım basit ve anlaşılır, ancak bir dezavantajı var. Uygulamamızdaki yetkilendirme şeması karmaşık olabilir ve tüm kuralları tek bir yerde tanımlarsak çok büyük, karmaşık ve okunması zor hale gelir. Bu nedenle, genellikle açıklama tabanlı yapılandırmayı kullanmayı tercih ederim.
Spring Security çerçevesi, web güvenliği için aşağıdaki ek açıklamaları tanımlar:
-
@PreAuthorize
, Spring Expression Language'i destekler ve yöntemi çalıştırmadan önce ifade tabanlı erişim denetimi sağlamak için kullanılır. -
@PostAuthorize
, Spring Expression Language'i destekler ve yöntemi çalıştırdıktan sonra ifadeye dayalı erişim kontrolü sağlamak için kullanılır (yöntem sonucuna erişme yeteneği sağlar). -
@PreFilter
, Spring Expression Language'i destekler ve tanımladığımız özel güvenlik kurallarına göre yöntemi çalıştırmadan önce koleksiyonu veya dizileri filtrelemek için kullanılır. -
@PostFilter
, Spring Expression Language'i destekler ve tanımladığımız özel güvenlik kurallarına göre yöntemi yürüttükten sonra döndürülen koleksiyon veya dizileri filtrelemek için kullanılır (yöntem sonucuna erişme yeteneği sağlar). -
@Secured
, Spring Expression Language'i desteklemez ve bir yöntemdeki rollerin listesini belirtmek için kullanılır. -
@RolesAllowed
, Spring Expression Language'i desteklemez ve JSR 250'nin@Secured
notunun eşdeğer notudur.
Bu ek açıklamalar varsayılan olarak devre dışıdır ve uygulamamızda aşağıdaki gibi etkinleştirilebilir:
@EnableWebSecurity @EnableGlobalMethodSecurity( securedEnabled = true, jsr250Enabled = true, prePostEnabled = true ) public class SecurityConfig extends WebSecurityConfigurerAdapter { // Details omitted for brevity }
securedEnabled = true
@Secured
ek açıklamasını etkinleştirir.
jsr250Enabled = true
@RolesAllowed
ek açıklamasını etkinleştirir.
prePostEnabled = true
@PreAuthorize
, @PostAuthorize
, @PreFilter
, @PostFilter
ek açıklamalarını etkinleştirir.
Bunları etkinleştirdikten sonra, API uç noktalarımızda aşağıdaki gibi rol tabanlı erişim ilkeleri uygulayabiliriz:
@Api(tags = "UserAdmin") @RestController @RequestMapping(path = "api/admin/user") @RolesAllowed(Role.USER_ADMIN) public class UserAdminApi { // Details omitted for brevity } @Api(tags = "Author") @RestController @RequestMapping(path = "api/author") public class AuthorApi { // Details omitted for brevity @RolesAllowed(Role.AUTHOR_ADMIN) @PostMapping public void create() { } @RolesAllowed(Role.AUTHOR_ADMIN) @PutMapping("{id}") public void edit() { } @RolesAllowed(Role.AUTHOR_ADMIN) @DeleteMapping("{id}") public void delete() { } @GetMapping("{id}") public void get() { } @GetMapping("{id}/book") public void getBooks() { } @PostMapping("search") public void search() { } } @Api(tags = "Book") @RestController @RequestMapping(path = "api/book") public class BookApi { // Details omitted for brevity @RolesAllowed(Role.BOOK_ADMIN) @PostMapping public BookView create() { } @RolesAllowed(Role.BOOK_ADMIN) @PutMapping("{id}") public void edit() { } @RolesAllowed(Role.BOOK_ADMIN) @DeleteMapping("{id}") public void delete() { } @GetMapping("{id}") public void get() { } @GetMapping("{id}/author") public void getAuthors() { } @PostMapping("search") public void search() { } }
Güvenlik açıklamalarının hem sınıf düzeyinde hem de yöntem düzeyinde sağlanabileceğini lütfen unutmayın.
Gösterilen örnekler basittir ve gerçek dünya senaryolarını temsil etmez, ancak Spring Security zengin bir açıklama seti sağlar ve bunları kullanmayı seçerseniz karmaşık bir yetkilendirme şemasını yönetebilirsiniz.
Rol Adı Varsayılan Öneki
Bu ayrı alt bölümde, birçok yeni kullanıcının kafasını karıştıran bir ince ayrıntıyı daha vurgulamak istiyorum.
Spring Security çerçevesi iki terimi birbirinden ayırır:
-
Authority
, bireysel bir izni temsil eder. -
Role
, bir grup izinleri temsil eder.
Her ikisi de GrantedAuthority
adlı tek bir arabirimle temsil edilebilir ve daha sonra Spring Security ek açıklamalarında Spring Expression Language ile aşağıdaki gibi kontrol edilebilir:
-
Authority
: @PreAuthorize(“hasAuthority('EDIT_BOOK')”) -
Role
: @PreAuthorize(“hasRole('BOOK_ADMIN')”)
Bu iki terim arasındaki farkı daha açık hale getirmek için Spring Security çerçevesi, varsayılan olarak rol adına bir ROLE_
öneki ekler. Bu nedenle, BOOK_ADMIN adlı bir rolü kontrol etmek yerine, BOOK_ADMIN
adlı bir rolü kontrol ROLE_BOOK_ADMIN
.
Şahsen, bu davranışı kafa karıştırıcı buluyorum ve uygulamalarımda devre dışı bırakmayı tercih ediyorum. Spring Security yapılandırmasında aşağıdaki gibi devre dışı bırakılabilir:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // Details omitted for brevity @Bean GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix } }
Spring Security ile test etme
Spring Security çerçevesini kullanırken uç noktalarımızı birim veya entegrasyon testleri ile test etmek için, spring-boot-starter-test
ile birlikte spring-security-test
bağımlılığını eklememiz gerekiyor. pom.xml
derleme dosyamız şöyle görünecek:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
Bu bağımlılık, test işlevlerimize güvenlik bağlamı eklemek için kullanılabilecek bazı ek açıklamalara erişmemizi sağlar.
Bu ek açıklamalar şunlardır:
-
@WithMockUser
, alaylı bir kullanıcıyla çalışmayı taklit etmek için bir test yöntemine eklenebilir. -
@WithUserDetails
,UserDetails
döndürülenUserDetailsService
ile çalıştırmayı taklit etmek için bir test yöntemine eklenebilir. -
@WithAnonymousUser
, anonim bir kullanıcıyla çalışmayı taklit etmek için bir test yöntemine eklenebilir. Bu, bir kullanıcı testlerin çoğunu belirli bir kullanıcı olarak çalıştırmak ve anonim olmak için birkaç yöntemi geçersiz kılmak istediğinde kullanışlıdır. -
@WithSecurityContext
, hangiSecurityContext
kullanılacağını belirler ve yukarıda açıklanan üç ek açıklamanın tümü buna dayalıdır. Belirli bir kullanım durumumuz varsa, istediğimiz herhangi birSecurityContext
oluşturmak için@WithSecurityContext
kullanan kendi ek açıklamamızı oluşturabiliriz. Tartışması makalemizin kapsamı dışındadır ve daha fazla ayrıntı için lütfen Spring Security belgelerine bakın.
Testleri belirli bir kullanıcıyla çalıştırmanın en kolay yolu @WithMockUser
ek açıklamasını kullanmaktır. Bununla bir sahte kullanıcı oluşturabilir ve testi aşağıdaki gibi çalıştırabiliriz:
@Test @WithMockUser(username="[email protected]", roles={"USER_ADMIN"}) public void test() { // Details omitted for brevity }
Yine de bu yaklaşımın birkaç dezavantajı var. İlk olarak, sahte kullanıcı mevcut değildir ve daha sonra veritabanından kullanıcı bilgilerini sorgulayan entegrasyon testini çalıştırırsanız, test başarısız olur. İkincisi, sahte kullanıcı, Spring çerçevesinin UserDetails
arabiriminin dahili uygulaması olan org.springframework.security.core.userdetails.User
sınıfının örneğidir ve kendi uygulamamız varsa, bu daha sonra, sırasında çatışmalara neden olabilir. Test uygulaması.
Önceki dezavantajlar uygulamamız için engelleyiciyse, o zaman @WithUserDetails
ek açıklaması gidilecek yoldur. Özel UserDetails
ve UserDetailsService
uygulamalarımız olduğunda kullanılır. Kullanıcının var olduğunu varsayar, bu nedenle testleri çalıştırmadan önce veritabanında gerçek satırı oluşturmamız veya UserDetailsService
sahte örneğini sağlamamız gerekir.
Bu ek açıklamayı şu şekilde kullanabiliriz:
@Test @WithUserDetails("[email protected]") public void test() { // Details omitted for brevity }
Bu, örnek projemizin entegrasyon testlerinde tercih edilen bir açıklamadır çünkü yukarıda bahsedilen arayüzlerin özel uygulamalarına sahibiz.
@WithAnonymousUser
kullanmak, anonim bir kullanıcı olarak çalışmaya izin verir. Bu, özellikle çoğu testi belirli bir kullanıcıyla, ancak birkaç testi anonim kullanıcı olarak çalıştırmak istediğinizde kullanışlıdır. Örneğin, aşağıdakiler sahte bir kullanıcıyla test1 ve test2 test senaryolarını ve anonim bir kullanıcıyla test3'ü çalıştırır:
@SpringBootTest @AutoConfigureMockMvc @WithMockUser public class WithUserClassLevelAuthenticationTests { @Test public void test1() { // Details omitted for brevity } @Test public void test2() { // Details omitted for brevity } @Test @WithAnonymousUser public void test3() throws Exception { // Details omitted for brevity } }
Toplama
Son olarak, Spring Security çerçevesinin muhtemelen herhangi bir güzellik yarışmasını kazanamayacağını ve kesinlikle dik bir öğrenme eğrisi olduğunu belirtmek isterim. İlk yapılandırma karmaşıklığı nedeniyle yerel bir çözümle değiştirildiği birçok durumla karşılaştım. Ancak geliştiriciler, içindekileri anladıktan ve ilk yapılandırmayı kurmayı başardıktan sonra, kullanımı nispeten kolay hale gelir.
Bu yazıda, konfigürasyonun tüm ince ayrıntılarını göstermeye çalıştım ve umarım örnekleri faydalı bulacaksınız. Eksiksiz kod örnekleri için lütfen örnek Spring Security projemin Git deposuna bakın.