RBAC ve Roller
Bir uygulamada yüzlerce kullanıcı ve düzinelerce endpoint olabilir. Her kullanıcı için her endpoint'e ayrı ayrı yetki tanımlamak yönetilemez. RBAC (Role-Based Access Control), kullanıcılara roller atayarak ve rollere yetkiler bağlayarak bu karmaşıklığı yönetilebilir kılar. Spring Security, RBAC modelini GrantedAuthority sistemiyle destekler.
Role vs Authority (Permission)
Spring Security'de role ve authority teknik olarak aynı mekanizmayı kullanır (GrantedAuthority), ama kavramsal olarak farklı seviyelerdedir:
Role (Rol): Kullanıcının genel görevini tanımlar — ADMIN, USER, EDITOR, MODERATOR. Geniş kapsamlı yetki grubudur. Spring Security convention'ında ROLE_ prefix'i ile saklanır.
Authority/Permission (Yetki/İzin): Spesifik bir işlemi yapabilme iznidir — USER_CREATE, REPORT_EXPORT, ORDER_DELETE. Granüler kontroldür.
// Role kullanımı
@PreAuthorize("hasRole('ADMIN')") // ROLE_ADMIN authority'sini arar
public void deleteUser(Long id) { ... }
// Authority kullanımı
@PreAuthorize("hasAuthority('USER_DELETE')") // Doğrudan USER_DELETE arar
public void deleteUser(Long id) { ... }GrantedAuthority
GrantedAuthority arayüzü, tek bir yetkiyi temsil eder. SimpleGrantedAuthority en yaygın implementasyonudur:
// Role olarak
GrantedAuthority roleAdmin = new SimpleGrantedAuthority("ROLE_ADMIN");
GrantedAuthority roleUser = new SimpleGrantedAuthority("ROLE_USER");
// Permission olarak
GrantedAuthority canCreate = new SimpleGrantedAuthority("USER_CREATE");
GrantedAuthority canDelete = new SimpleGrantedAuthority("USER_DELETE");
// Kullanıcıya atama
UserDetails admin = User.builder()
.username("admin")
.password(encoder.encode("admin123"))
.authorities("ROLE_ADMIN", "USER_CREATE", "USER_DELETE", "REPORT_EXPORT")
.build();RBAC Model: Kullanıcı → Rol → İzin
Profesyonel uygulamalarda üç katmanlı bir model kullanılır:
Kullanıcı ──M:N──▶ Rol ──M:N──▶ İzin (Permission)
Örnek:
Ali ──▶ ADMIN ──▶ USER_CREATE, USER_READ, USER_UPDATE, USER_DELETE
──▶ REPORT_VIEW, REPORT_EXPORT
Ayşe ──▶ EDITOR ──▶ ARTICLE_CREATE, ARTICLE_EDIT, ARTICLE_PUBLISH
──▶ USER ──▶ USER_READJPA Entity tasarımı:
@Entity
@Table(name = "permissions")
public class Permission {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name; // USER_CREATE, REPORT_EXPORT
}
@Entity
@Table(name = "roles")
public class Role {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name; // ROLE_ADMIN, ROLE_USER
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_permissions",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id"))
private Set<Permission> permissions = new HashSet<>();
}UserDetailsService'te rolleri ve permission'ları birleştirme:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Bulunamadı: " + username));
Set<GrantedAuthority> authorities = new HashSet<>();
for (Role role : user.getRoles()) {
// Rolün kendisini ekle
authorities.add(new SimpleGrantedAuthority(role.getName()));
// Rolün permission'larını ekle
for (Permission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getName()));
}
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities);
}Role Hierarchy (Rol Hiyerarşisi)
Çoğu uygulamada roller hiyerarşiktir: ADMIN, EDITOR'ın tüm yetkilerine sahiptir; EDITOR, USER'ın tüm yetkilerine sahiptir. Spring Security bunu RoleHierarchy ile destekler:
@Bean
public RoleHierarchy roleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("""
ROLE_ADMIN > ROLE_EDITOR
ROLE_EDITOR > ROLE_USER
ROLE_USER > ROLE_GUEST
""");
}
// Spring Security 6.1+ ile MethodSecurityExpressionHandler'a bağlama
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler handler =
new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy);
return handler;
}Bu yapılandırmayla:
hasRole('USER')kontrolü: ADMIN, EDITOR ve USER içintrue.hasRole('EDITOR')kontrolü: ADMIN ve EDITOR içintrue.hasRole('ADMIN')kontrolü: Sadece ADMIN içintrue.
HttpSecurity'de Role ve Authority Kullanımı
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
// Role bazlı (ROLE_ prefix otomatik eklenir)
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/editor/**").hasAnyRole("EDITOR", "ADMIN")
// Authority bazlı (doğrudan string karşılaştırması)
.requestMatchers(HttpMethod.DELETE, "/api/users/**")
.hasAuthority("USER_DELETE")
.requestMatchers("/api/reports/export")
.hasAuthority("REPORT_EXPORT")
.requestMatchers("/", "/public/**").permitAll()
.anyRequest().authenticated()
)
.build();
}hasRole vs hasAuthority
| Metot | Prefix | Örnek |
|---|---|---|
hasRole("ADMIN") | ROLE_ otomatik eklenir | ROLE_ADMIN arar |
hasAnyRole("USER", "ADMIN") | ROLE_ otomatik eklenir | ROLE_USER veya ROLE_ADMIN |
hasAuthority("USER_DELETE") | Prefix yok | USER_DELETE arar |
hasAnyAuthority(...) | Prefix yok | Birden fazla authority |
RBAC vs PBAC (Permission-Based Access Control)
RBAC'ta rollere göre yetkilendirme yapılır. PBAC'ta ise rollere bağlı kalmadan doğrudan permission'lara göre erişim kontrol edilir:
RBAC Yaklaşımı:
// "ADMIN rolüne sahip mi?" sorusu sorulur
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
// Sorun: Yeni bir "MODERATOR" rolü de silme yetkisine sahipse
// bu kontrolü değiştirmek gerekir
@PreAuthorize("hasAnyRole('ADMIN', 'MODERATOR')") // Her yerde güncelleme!PBAC Yaklaşımı (Önerilen):
// "USER_DELETE izni var mı?" sorusu sorulur — rol fark etmez
@PreAuthorize("hasAuthority('USER_DELETE')")
public void deleteUser(Long id) { ... }
// Yeni bir rol eklendiğinde sadece rol-permission mapping güncellenir
// Kod değişmez — Single Responsibility PrincipleRBAC + PBAC Hybrid (En iyi pratik):
URL seviyesinde → Rolle kontrol:
.requestMatchers("/admin/**").hasRole("ADMIN")Method seviyesinde → Permission ile kontrol:
@PreAuthorize("hasAuthority('USER_DELETE')")
Dinamik Rol Yönetimi
Üretim uygulamalarında roller hardcode değil, veritabanından yönetilir. Admin panelinden rol oluşturma, silme ve permission atama yapılabilir:
@RestController
@RequestMapping("/api/admin/roles")
@PreAuthorize("hasRole('SUPER_ADMIN')")
public class RoleManagementController {
private final RoleService roleService;
@PostMapping
public ResponseEntity<RoleDto> createRole(@RequestBody CreateRoleRequest request) {
// Yeni rol oluştur: { "name": "CONTENT_EDITOR", "permissions": ["ARTICLE_CREATE", "ARTICLE_EDIT"] }
Role role = roleService.createRole(request.getName(), request.getPermissions());
return ResponseEntity.status(HttpStatus.CREATED).body(RoleDto.from(role));
}
@PutMapping("/{roleId}/permissions")
public ResponseEntity<RoleDto> updatePermissions(
@PathVariable Long roleId,
@RequestBody Set<String> permissionNames) {
// Mevcut role yeni permission'lar ata
Role role = roleService.updatePermissions(roleId, permissionNames);
return ResponseEntity.ok(RoleDto.from(role));
}
@PostMapping("/users/{userId}/roles")
public ResponseEntity<Void> assignRole(
@PathVariable Long userId,
@RequestBody AssignRoleRequest request) {
roleService.assignRoleToUser(userId, request.getRoleId());
return ResponseEntity.ok().build();
}
}Yaygın Hatalar
1. ROLE_ prefix karışıklığı:
// ❌ hasRole zaten ROLE_ ekler — çift prefix olur
.hasRole("ROLE_ADMIN") // ROLE_ROLE_ADMIN arar!
// ✅ Doğru kullanım
.hasRole("ADMIN") // ROLE_ADMIN arar
.hasAuthority("ROLE_ADMIN") // ROLE_ADMIN arar (eşdeğer)2. Authority'leri getAuthorities()'den döndürmemek:
// ❌ UserDetails'ta rolleri döndürmeyi unutmak
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList(); // Kullanıcının hiçbir yetkisi yok!
}
// ✅ Rolleri ve permission'ları dahil etmek
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();
for (Role role : this.roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
role.getPermissions().forEach(p ->
authorities.add(new SimpleGrantedAuthority(p.getName())));
}
return authorities;
}⚠️ Dikkat:
hasRole("ADMIN")aslındaROLE_ADMINauthority'sini arar. Veritabanında authority'leriniziROLE_prefix'i ile kaydedin veyahasAuthority()kullanın.
💡 Özet: RBAC, kullanıcılara roller atayarak kaynaklara erişimi yönetir.
hasRole()otomatikROLE_prefix ekler,hasAuthority()eklemez. Role hierarchy ile hiyerarşik yetkilendirme sağlanır. PBAC (permission-based) ile daha granüler kontrol mümkündür. Dinamik rol yönetimi için rol ve permission'lar veritabanında tutulmalı, admin panelinden yönetilmelidir.
AI Asistan
Sorularını yanıtlamaya hazır