← Kursa Dön
📄 Text · 25 min

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_READ

JPA 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çin true.

  • hasRole('EDITOR') kontrolü: ADMIN ve EDITOR için true.

  • hasRole('ADMIN') kontrolü: Sadece ADMIN için true.

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

MetotPrefixÖrnek
hasRole("ADMIN")ROLE_ otomatik eklenirROLE_ADMIN arar
hasAnyRole("USER", "ADMIN")ROLE_ otomatik eklenirROLE_USER veya ROLE_ADMIN
hasAuthority("USER_DELETE")Prefix yokUSER_DELETE arar
hasAnyAuthority(...)Prefix yokBirden 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 Principle

RBAC + 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ında ROLE_ADMIN authority'sini arar. Veritabanında authority'lerinizi ROLE_ prefix'i ile kaydedin veya hasAuthority() kullanın.

💡 Özet: RBAC, kullanıcılara roller atayarak kaynaklara erişimi yönetir. hasRole() otomatik ROLE_ 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.