Home/Learn/Spring Boot/Role-Based Access

Role-Based Access

Intermediate
Security

Spring Security's authorization model uses roles (ROLE_ADMIN) and authorities (fine-grained permissions). URL-level access rules in HttpSecurity handle coarse-grained control; method security handles fine-grained access within a use case.

Overview

Roles are prefixed authorities — ROLE_ADMIN is an authority, hasRole("ADMIN") is shorthand for hasAuthority("ROLE_ADMIN"). GrantedAuthority objects are loaded in UserDetails.getAuthorities() and stored in the SecurityContext. URL-level authorization with authorizeHttpRequests() is coarse-grained (protect all admin endpoints). Method-level security with @PreAuthorize is fine-grained (a user can access their own data but not others'). Use roles for coarse access; custom permissions or ownership checks for fine-grained control.

Roles, Authorities and URL-Level Access

Load roles from the database in UserDetailsService. In SecurityFilterChain, use hasRole(), hasAnyRole(), or hasAuthority() to protect URL patterns.

Java — loading roles, URL-level access rules
// Loading roles from DB in UserDetailsService
@Override
public UserDetails loadUserByUsername(String email) {
    User user = userRepository.findByEmail(email).orElseThrow(...);
    return org.springframework.security.core.userdetails.User
        .withUsername(user.getEmail())
        .password(user.getPasswordHash())
        .roles(user.getRole().name()) // adds "ROLE_" prefix automatically
        // Or for fine-grained authorities:
        // .authorities(user.getPermissions().stream()
        //     .map(p -> new SimpleGrantedAuthority(p.name()))
        //     .toList())
        .build();
}

// URL-level authorization in SecurityConfig
.authorizeHttpRequests(auth -> auth
    // Public
    .requestMatchers("/api/v1/auth/**").permitAll()
    .requestMatchers(HttpMethod.GET, "/api/v1/courses/**").permitAll()

    // Role-based
    .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")

    // Multiple roles
    .requestMatchers("/api/v1/cohorts/**").hasAnyRole("ADMIN", "INSTRUCTOR")

    // Fine-grained authority
    .requestMatchers(HttpMethod.POST, "/api/v1/blog/**").hasAuthority("WRITE_BLOG")

    // Catch-all
    .anyRequest().authenticated()
)

Access Current User's Data — Ownership Check

Roles alone can't express "a user can only access their own data." Use the authenticated principal from SecurityContext in the service layer to enforce ownership.

Java — ownership enforcement with SecurityContext
// ✅ Ownership check in service layer
@Service
public class SubmissionService {

    public SubmissionDto getSubmission(String submissionId,
                                       String requestingUserId) {
        Submission sub = submissionRepository.findById(submissionId)
            .orElseThrow(() -> new NotFoundException(submissionId));

        // Check ownership — admins bypass
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        boolean isAdmin = auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));

        if (!isAdmin && !sub.getUserId().equals(requestingUserId)) {
            throw new AccessDeniedException("Cannot access another user's submission");
        }
        return submissionMapper.toDto(sub);
    }
}

// Controller — inject principal from SecurityContext
@RestController
@RequestMapping("/api/v1/submissions")
public class SubmissionController {

    @GetMapping("/{id}")
    public SubmissionDto getSubmission(
            @PathVariable String id,
            @AuthenticationPrincipal String userId) {
        return submissionService.getSubmission(id, userId);
    }
}

Key Points to Remember

  • 1Roles are prefixed authorities — ROLE_ADMIN; hasRole("ADMIN") is shorthand for hasAuthority("ROLE_ADMIN").
  • 2URL-level access rules in HttpSecurity are coarse-grained — evaluated in the order you declare them.
  • 3More specific matchers must come before general ones — .anyRequest().authenticated() must be last.
  • 4For ownership checks, verify the principal in the service layer — URL rules can't express "own resource".
  • 5Load authorities from the database in UserDetailsService — they travel with the JWT or session.
  • 6Use hasAuthority() for fine-grained permissions (WRITE_BLOG, DELETE_COURSE) separate from broad roles.

Interview Questions

Sign in to ask Aria
1

What is the difference between a role and an authority in Spring Security?

EasyWipro
2

How do you ensure users can only access their own data?

MediumAmazon
3

Why must .anyRequest().authenticated() be the last rule in authorizeHttpRequests?

EasyInfosys
4

What is the difference between hasRole("ADMIN") and hasAuthority("ROLE_ADMIN")?

EasyTCS
5

How would you implement multi-tenancy access control (users only see their org's data)?

HardAtlassian

Ask Aria about Role-Based Access

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…