Spring Boot Roles and Authorization

Logging in proves who a user is. Authorization decides what that user may do. A shop clerk may view orders, while only a manager may delete them. Roles give Spring Security a simple way to express these rules.

Authentication always comes first, and authorization follows. A request without a verified identity never reaches the permission check.

The Office Building Idea

Picture an office with colored access cards. A visitor card opens the lobby. An employee card opens the lobby and the work floors. A manager card opens those areas and the records room. The card does not change who the person is; it changes which doors open. Roles act as those cards.

 Role         Lobby   Work Floor   Records Room
 -----------------------------------------------
 VISITOR       yes       no           no
 EMPLOYEE      yes       yes          no
 MANAGER       yes       yes          yes

Roles and Authorities

Spring Security uses two related words.

  • A role is a broad group such as ADMIN or USER.
  • An authority is a fine-grained permission such as BOOK_DELETE.

Spring stores roles as authorities with the prefix ROLE_. The role ADMIN becomes the authority ROLE_ADMIN. Methods such as hasRole("ADMIN") add the prefix automatically.

The Request Journey

 Request
    |
 Authentication filter  -->  Who is this user?
    |
 Authorization filter   -->  Does the role allow this address?
    |
   yes --> Controller runs
    no --> 403 Forbidden

A missing or invalid login returns 401 Unauthorized. A valid user without permission receives 403 Forbidden.

Creating Users with Roles

An in-memory user store keeps examples short. Production systems load users from a database.

@Configuration
public class UserConfig {

    @Bean
    public UserDetailsService users(PasswordEncoder encoder) {
        UserDetails clerk = User.withUsername("clerk")
                .password(encoder.encode("clerk123"))
                .roles("USER")
                .build();

        UserDetails boss = User.withUsername("boss")
                .password(encoder.encode("boss123"))
                .roles("USER", "ADMIN")
                .build();

        return new InMemoryUserDetailsManager(clerk, boss);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

BCrypt hashes the passwords, so plain text never sits in memory or storage.

Protecting Addresses

The security filter chain maps address patterns to roles.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/books/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers(HttpMethod.DELETE, "/api/books/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

Rules run from top to bottom, and the first match wins. Place specific rules above general ones, and finish with anyRequest.

Common Rule Methods

MethodMeaning
permitAll()Open to everyone
authenticated()Any logged-in user
hasRole("ADMIN")Only the ADMIN role
hasAnyRole("A", "B")Either role works
hasAuthority("BOOK_DELETE")One exact permission
denyAll()Closed to everyone

Method-Level Security

Rules can sit directly on service or controller methods. Turn the feature on with one annotation.

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

@Service
public class BookService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteBook(Long id) {
        // delete logic
    }

    @PreAuthorize("hasRole('USER') and #owner == authentication.name")
    public void editOwnNote(String owner) {
        // edit logic
    }
}

@PreAuthorize runs before the method. The second example allows a user to edit only a note that belongs to the logged-in name.

Reading the Current User

@GetMapping("/api/me")
public String whoAmI(Authentication authentication) {
    return authentication.getName() + " " + authentication.getAuthorities();
}

Testing the Rules

curl -u clerk:clerk123 http://localhost:8080/api/books          # 200 OK
curl -u clerk:clerk123 -X DELETE http://localhost:8080/api/books/1   # 403 Forbidden
curl -u boss:boss123  -X DELETE http://localhost:8080/api/books/1    # 204 No Content

Authentication Versus Authorization in Practice

Authentication answers the question "who are you?" Authorization answers "what may you do?" Mixing the two causes real bugs. A logged-in customer remains authenticated while opening an admin page, and the system must still say no.

Picture a store with three kinds of users. Customers view products and place orders. Staff members update stock and process shipments. Administrators manage users and delete records. Each kind receives one role, and every endpoint lists the roles that may use it.

Plan the roles on paper before writing code. A short table of actions and roles exposes gaps early, such as an action that nobody can perform or one that everybody can perform by mistake. Review the table whenever a new endpoint appears.

Error replies matter too. A 401 status tells the client to log in. A 403 status tells the client that logging in again will not help. Clear messages reduce support questions and make client code simpler.

Best Practices

  • Give each user the smallest set of roles needed for their work.
  • Protect both addresses and sensitive methods for double safety.
  • Hash every password with BCrypt or a similar encoder.
  • Keep role names in constants to prevent spelling mistakes.
  • Write tests for allowed and denied cases.

Clear role rules keep private data private. A well-planned permission model protects users and keeps the system easy to audit.