Skip to main content

Week 6 Day 3 — Authorization Deep Dive

Goal

Today I want to understand authorization in Spring Security.

Main questions:

  1. What is authorization?
  2. What is the difference between roles and authorities?
  3. What does hasRole check?
  4. What does hasAuthority check?
  5. How do URL authorization rules work?
  6. Why does rule order matter?
  7. What is method security?
  8. What is @EnableMethodSecurity?
  9. What is @PreAuthorize?
  10. How do I use method parameters in security expressions?
  11. What are data-level permissions?
  12. Why are URL rules not enough?
  13. What are common exam traps?

1. Quick Review from Week 6 Day 2

In Day 2, I learned:

  • UserDetails is Spring Security’s user representation.
  • UserDetailsService loads users by username.
  • Username can be an email.
  • PasswordEncoder hashes and verifies passwords.
  • Store password hashes, not raw passwords.
  • AuthenticationManager authenticates credentials.
  • DaoAuthenticationProvider uses UserDetailsService and PasswordEncoder.
  • Authentication represents the login attempt or authenticated user.
  • roles("ADMIN") creates ROLE_ADMIN.
  • hasRole("ADMIN") checks ROLE_ADMIN.

Memory sentence:

Authentication = who are you?
Authorization = what can you do?

Today I go deeper into authorization.


2. What Is Authorization?

Authorization answers:

What is this user allowed to do?

Examples:

Can this user access /api/admin/users?
Can this user delete a task?
Can this user approve an invoice?
Can this user access client 10?
Can this user see another tenant's data?
Can this user export payroll documents?

Authorization happens after authentication.

First:

Who is the user?

Then:

What may this user do?

Memory sentence:

Authorization checks permissions after authentication.


3. Authentication vs Authorization

ConceptQuestionExample
AuthenticationWho are you?Login with email and password
AuthorizationWhat can you do?Only ADMIN can delete users

Example:

User logs in successfully.
Authentication succeeded.

User tries to call /api/admin/users.
Authorization checks if user has ADMIN role.

Memory sentence:

Authentication proves identity.
Authorization checks access.

4. 401 vs 403 Review

401 Unauthorized

Means:

The user is not authenticated.

Examples:

missing token
invalid token
wrong password
not logged in

403 Forbidden

Means:

The user is authenticated but not allowed.

Examples:

USER tries to access ADMIN endpoint
employee tries to access another tenant
normal user tries to delete another user's task

Memory sentence:

401 = who are you?
403 = I know who you are, but you are not allowed.

5. What Are Authorities?

An authority is a permission granted to a user.

Examples:

ROLE_USER
ROLE_ADMIN
TASK_READ
TASK_WRITE
TASK_DELETE
INVOICE_APPROVE
CLIENT_EXPORT

Spring Security stores authorities inside:

Authentication

Example:

principal = user@example.com
authorities = ROLE_USER, TASK_READ, TASK_WRITE

Memory sentence:

Authorities are permissions known by Spring Security.


6. What Are Roles?

A role is usually a broad group of permissions.

Examples:

USER
ADMIN
MANAGER
ACCOUNTANT
CONSULTANT

In Spring Security, roles are usually represented as authorities with prefix:

ROLE_

So role:

ADMIN

becomes authority:

ROLE_ADMIN

Memory sentence:

A role is usually an authority with ROLE_ prefix.


7. Role vs Authority

TopicRoleAuthority
Meaningbroad groupspecific permission
ExampleADMINTASK_DELETE
Stored formROLE_ADMINTASK_DELETE
Common checkhasRole("ADMIN")hasAuthority("TASK_DELETE")
Use casesimple apps, broad accessfine-grained permissions

Memory:

Role = group.
Authority = permission.

8. hasRole

Example:

.requestMatchers("/api/admin/**").hasRole("ADMIN")

This checks whether the user has authority:

ROLE_ADMIN

Important:

hasRole("ADMIN")

does not check for authority:

ADMIN

It checks for:

ROLE_ADMIN

Memory sentence:

hasRole("ADMIN") checks for ROLE_ADMIN.


9. hasAuthority

Example:

.requestMatchers("/api/tasks/**").hasAuthority("TASK_READ")

This checks exactly:

TASK_READ

Another example:

.requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")

This checks exactly:

ROLE_ADMIN

Memory sentence:

hasAuthority checks the exact authority string.


10. hasRole vs hasAuthority

CodeChecks for
hasRole("ADMIN")ROLE_ADMIN
hasAuthority("ROLE_ADMIN")ROLE_ADMIN
hasAuthority("TASK_READ")TASK_READ
hasRole("ROLE_ADMIN")usually wrong

Common mistake:

hasRole("ROLE_ADMIN")

Why wrong?

hasRole adds ROLE_ automatically.
This can become ROLE_ROLE_ADMIN.

Correct:

hasRole("ADMIN")

or:

hasAuthority("ROLE_ADMIN")

Memory sentence:

Use hasRole("ADMIN") or hasAuthority("ROLE_ADMIN"), not hasRole("ROLE_ADMIN").


11. Where Do Authorities Come From?

Authorities usually come from:

database roles
database permissions
JWT claims
OAuth2 scopes
LDAP groups
hardcoded in-memory users
custom AuthenticationProvider

Example with database user:

return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.build();

If user.getRole() returns:

ADMIN

then Spring creates:

ROLE_ADMIN

Memory sentence:

Authorization depends on authorities loaded during authentication.


12. Simple URL Authorization

Security config:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

Meaning:

/api/public/** is open
/api/admin/** needs ADMIN role
everything else needs login

Memory sentence:

URL authorization protects endpoints by request path and method.


13. HTTP Method Authorization

Example:

.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.requestMatchers(HttpMethod.DELETE, "/api/tasks/**").hasAuthority("TASK_DELETE")
.anyRequest().authenticated()
)

Meaning:

GET task endpoints need TASK_READ
POST task endpoints need TASK_WRITE
DELETE task endpoints need TASK_DELETE

This is more precise than path-only rules.

Memory sentence:

URL rules can match both path and HTTP method.


14. Rule Order Matters

Good:

.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)

Bad:

.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
.requestMatchers("/api/public/**").permitAll()
)

Why bad?

anyRequest matches everything.
Rules after anyRequest are unreachable or invalid.

Memory sentence:

Specific rules first. General rules last.


15. permitAll, authenticated, denyAll

Common rules:

permitAll()
authenticated()
denyAll()

Meaning:

RuleMeaning
permitAll()anyone can access
authenticated()user must be logged in
denyAll()nobody can access

Examples:

.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/internal/**").denyAll()
.anyRequest().authenticated()

16. hasAnyRole and hasAnyAuthority

Example:

.requestMatchers("/api/reports/**").hasAnyRole("ADMIN", "MANAGER")

Checks for:

ROLE_ADMIN or ROLE_MANAGER

Example:

.requestMatchers("/api/tasks/**").hasAnyAuthority("TASK_READ", "TASK_WRITE")

Checks for:

TASK_READ or TASK_WRITE

Memory sentence:

hasAnyRole / hasAnyAuthority means at least one of them is enough.


17. URL Authorization Is Coarse-Grained

URL rules are good for broad access.

Examples:

/api/admin/** needs ADMIN
/api/auth/** is public
GET /api/tasks/** needs TASK_READ
POST /api/tasks/** needs TASK_WRITE

But URL rules often cannot answer:

Can this user access client 10?
Does this task belong to the user's tenant?
Can this accountant approve this invoice?
Is this user the owner of this record?

These are data-level permissions.

Memory sentence:

URL authorization protects endpoints, but not always specific data.


18. Data-Level Permission

Data-level permission means:

Access depends on the specific data being accessed.

Example:

GET /api/clients/10/tasks

URL rule can check:

user is authenticated
user has role USER

But service must check:

Does this user belong to client 10?
Does this user belong to the same tenant?
Is this user allowed to see these tasks?

Memory sentence:

Data-level permission checks access to a specific resource, not just a URL.


19. Tenant Permission Example

Imagine a multi-tenant app.

User:

userId = 5
tenantId = 100
role = USER

Task:

taskId = 20
tenantId = 200

Even if the user is authenticated and has role USER:

user should not access task from tenant 200

Why?

different tenant

Memory sentence:

In multi-tenant apps, authorization must check tenant ownership.


20. Service-Level Permission Check

Example:

@Service
public class TaskService {

private final TaskRepository taskRepository;
private final PermissionService permissionService;

public TaskService(
TaskRepository taskRepository,
PermissionService permissionService
) {
this.taskRepository = taskRepository;
this.permissionService = permissionService;
}

@Transactional(readOnly = true)
public TaskDto findById(Long taskId, AppUserPrincipal currentUser) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

if (!permissionService.canAccessTask(currentUser, task)) {
throw new AccessDeniedException("No access to this task");
}

return toDto(task);
}
}

This checks real business permission.

Memory sentence:

Service methods should protect business data access.


21. AccessDeniedException

If authenticated user is not allowed, throw:

AccessDeniedException

Example:

throw new AccessDeniedException("No access to this task");

Spring Security commonly maps this to:

403 Forbidden

Memory sentence:

AccessDeniedException means authenticated but forbidden.


22. Method Security

Method security means:

Authorization rules are placed on methods, usually service methods.

Example:

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

This method can run only if the user has role ADMIN.

Memory sentence:

Method security protects method calls, not just URLs.


23. Enabling Method Security

To use annotations like @PreAuthorize, enable method security:

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

Or put it on existing security config:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}

Important:

Adding spring-boot-starter-security does not automatically enable method security.

Memory sentence:

Use @EnableMethodSecurity to activate method security annotations.


24. @PreAuthorize

@PreAuthorize checks authorization before the method runs.

Example:

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

If user has ADMIN:

method runs

If user does not have ADMIN:

method is blocked
AccessDeniedException

Memory sentence:

@PreAuthorize checks before method execution.


25. @PreAuthorize with Authority

Example:

@PreAuthorize("hasAuthority('TASK_DELETE')")
public void deleteTask(Long taskId) {
taskRepository.deleteById(taskId);
}

This checks exact authority:

TASK_DELETE

If the user only has:

TASK_READ

then the method is blocked.


26. @PreAuthorize with Multiple Roles

Example:

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<ReportDto> getReports() {
return reportRepository.findAllReports();
}

Meaning:

ADMIN or MANAGER can call this method

Another example:

@PreAuthorize("hasRole('ADMIN') or hasAuthority('REPORT_READ')")
public List<ReportDto> getReports() {
return reportRepository.findAllReports();
}

Memory sentence:

@PreAuthorize supports security expressions.


27. Method Parameters in @PreAuthorize

@PreAuthorize can use method parameters.

Example:

@PreAuthorize("#userId == authentication.principal.id")
public UserDto getOwnProfile(Long userId) {
return userService.findById(userId);
}

Meaning:

user can access only their own profile

Another example:

@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public UserDto getProfile(Long userId) {
return userService.findById(userId);
}

Meaning:

ADMIN can access any profile
normal user can access only own profile

Memory sentence:

#parameterName refers to a method argument in security expressions.


28. Custom Permission Method in @PreAuthorize

For complex checks, call a Spring bean.

Permission service:

@Component("permissionService")
public class PermissionService {

public boolean canAccessTask(Long taskId, Authentication authentication) {
String username = authentication.getName();

// load user/task and check tenant, ownership, role, etc.
return true;
}
}

Service method:

@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
public TaskDto findTask(Long taskId) {
return taskRepository.findById(taskId)
.map(this::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));
}

Meaning:

Before method runs, Spring calls permissionService.canAccessTask(...)

Memory sentence:

For real business permissions, call a permission bean from @PreAuthorize.


29. authentication in Expressions

Inside @PreAuthorize, I can use:

authentication

Example:

@PreAuthorize("authentication.name == #email")
public UserDto findByEmail(String email) {
return userService.findByEmail(email);
}

authentication.name usually returns:

current username

If username is email, then:

authentication.name = current user's email

Memory sentence:

authentication gives access to the current authenticated user in expressions.


30. Custom Principal in Expressions

If principal is custom:

public class AppUserPrincipal implements UserDetails {

private final Long id;
private final Long tenantId;
private final String email;

public Long getId() {
return id;
}

public Long getTenantId() {
return tenantId;
}

@Override
public String getUsername() {
return email;
}
}

Then expression:

@PreAuthorize("#tenantId == authentication.principal.tenantId")
public List<TaskDto> findTenantTasks(Long tenantId) {
return taskRepository.findByTenantId(tenantId)
.stream()
.map(this::toDto)
.toList();
}

Meaning:

User can only access their own tenant.

Memory sentence:

Custom principal fields can be used in method security expressions.


31. @PostAuthorize

@PostAuthorize checks after the method returns.

Example:

@PostAuthorize("returnObject.ownerEmail == authentication.name")
public DocumentDto findDocument(Long id) {
return documentRepository.findById(id)
.map(this::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Document", id));
}

Meaning:

method runs first
then Spring checks returned object
only owner can receive it

Use carefully.

Why?

the data is loaded before authorization decision
may be less efficient
may have side effects if used on write methods

Memory sentence:

@PostAuthorize checks after method execution.


32. Prefer @PreAuthorize for Most Cases

Usually prefer:

@PreAuthorize(...)

because:

it blocks method before execution
avoids unnecessary database work
avoids side effects
clearer for write operations

Use @PostAuthorize when the decision depends on the returned object.

Memory sentence:

Prefer pre-checks unless the decision needs the return value.


33. @Secured

Another method security annotation:

@Secured("ROLE_ADMIN")
public void deleteUser(Long id) {
}

It is simpler but less expressive than @PreAuthorize.

@PreAuthorize can use:

roles
authorities
method parameters
authentication
custom beans
SpEL expressions

Memory sentence:

@PreAuthorize is more expressive than @Secured.


34. JSR-250 Annotations

Spring Security can also support annotations like:

@RolesAllowed("ADMIN")
@PermitAll
@DenyAll

But these may require enabling JSR-250 support:

@EnableMethodSecurity(jsr250Enabled = true)

For Spring Security learning, focus first on:

@PreAuthorize

Memory sentence:

@PreAuthorize is the most flexible method security annotation to learn first.


35. Method Security Works on Spring Beans

Method security applies to Spring-managed beans.

Good:

@Service
public class TaskService {

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto findTask(Long id) {
...
}
}

Not useful on objects that are not Spring beans.

Example:

new TaskService()

This bypasses Spring and its proxies.

Memory sentence:

Method security works through Spring-managed beans.


36. Self-Invocation Trap Again

Similar to @Transactional, method security is often proxy-based.

Problem:

@Service
public class TaskService {

public TaskDto outer(Long id) {
return inner(id);
}

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto inner(Long id) {
return findTask(id);
}
}

If outer() calls inner() inside the same class, the call may bypass the security proxy.

Better:

put annotation on outer public method
move secured method to another Spring bean
avoid relying on internal calls for security

Memory sentence:

Method security can be bypassed by self-invocation.


37. URL Security vs Method Security

TopicURL SecurityMethod Security
WhereSecurityFilterChainservice/controller methods
StyleDSL configannotations
Best forendpoint-level rulesbusiness/service-level rules
Example/api/admin/**@PreAuthorize("hasRole('ADMIN')")
Can use method argsnoyes
Data-level checkslimitedgood

Memory sentence:

URL security is coarse-grained. Method security is fine-grained.


38. Use Both URL and Method Security

Good design:

URL security:
- public endpoints
- admin area
- authenticated API
- HTTP method rules

Method security:
- service-level roles
- task ownership
- tenant access
- business action permission

Example URL config:

.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)

Example service method:

@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
public TaskDto findTask(Long taskId) {
...
}

Memory sentence:

Use URL rules for broad access and method security for business access.


39. Controller with Current User

Controller:

@GetMapping("/api/tasks/{taskId}")
public TaskDto getTask(
@PathVariable Long taskId,
@AuthenticationPrincipal AppUserPrincipal principal
) {
return taskService.findTask(taskId, principal);
}

Service:

@Transactional(readOnly = true)
public TaskDto findTask(Long taskId, AppUserPrincipal principal) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

if (!task.getTenantId().equals(principal.getTenantId())) {
throw new AccessDeniedException("No access to this task");
}

return toDto(task);
}

This is explicit and easy to understand.

Memory sentence:

Passing current user to service makes data permission checks clear.


40. Method Security Alternative

Instead of passing principal and checking manually:

@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
@Transactional(readOnly = true)
public TaskDto findTask(Long taskId) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

return toDto(task);
}

Permission service:

@Component("permissionService")
public class PermissionService {

private final TaskRepository taskRepository;

public PermissionService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

public boolean canAccessTask(Long taskId, Authentication authentication) {
AppUserPrincipal principal = (AppUserPrincipal) authentication.getPrincipal();

return taskRepository.existsByIdAndTenantId(
taskId,
principal.getTenantId()
);
}
}

Repository:

boolean existsByIdAndTenantId(Long id, Long tenantId);

This checks access before loading full task details.

Memory sentence:

Permission checks can use efficient repository methods like existsBy....


41. Avoid Trusting Client-Sent User IDs

Bad request:

POST /api/tasks
Content-Type: application/json

{
"title": "Secret task",
"userId": 123
}

Bad service:

public TaskDto create(CreateTaskRequest request) {
UserEntity user = userRepository.findById(request.userId()).orElseThrow();
...
}

Problem:

client can pretend to be another user

Better:

take current user from Authentication/SecurityContext
ignore userId from request for ownership

Good:

public TaskDto create(CreateTaskRequest request, AppUserPrincipal principal) {
UserEntity user = userRepository.findById(principal.getId()).orElseThrow();
...
}

Memory sentence:

Never trust client-sent user identity when authenticated user is available.


42. Avoid Only Frontend Authorization

Bad assumption:

The button is hidden in React, so backend is safe.

Wrong.

A user can still call the API directly with:

Postman
curl
browser devtools
custom script

Backend must enforce authorization.

Memory sentence:

Frontend hides buttons. Backend enforces security.


43. Authorization in Repository Queries

Sometimes the safest query includes access scope.

Bad:

Optional<TaskEntity> findById(Long id);

Then later check tenant.

Better for reads:

Optional<TaskEntity> findByIdAndTenantId(Long id, Long tenantId);

Service:

@Transactional(readOnly = true)
public TaskDto findTask(Long taskId, AppUserPrincipal principal) {
TaskEntity task = taskRepository.findByIdAndTenantId(
taskId,
principal.getTenantId()
).orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

return toDto(task);
}

This avoids loading data outside the user's tenant.

Memory sentence:

Scope repository queries by tenant or owner when possible.


44. 404 vs 403 for Data Access

If user requests another tenant's task, should API return 403 or 404?

Both are possible design choices.

403

Means:

resource exists, but user is not allowed

404

Means:

resource not found from this user's perspective

Many APIs return 404 to avoid revealing whether a resource exists.

Example:

taskRepository.findByIdAndTenantId(taskId, principal.getTenantId())
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

Memory sentence:

For cross-tenant resources, 404 can avoid leaking existence.


45. Role Explosion Problem

Bad design:

ROLE_ADMIN
ROLE_TASK_READER
ROLE_TASK_WRITER
ROLE_INVOICE_APPROVER
ROLE_CLIENT_EXPORTER
ROLE_PAYROLL_MANAGER
ROLE_DOCUMENT_UPLOADER

If everything becomes a role, roles become messy.

Better:

roles = broad groups
authorities = specific permissions

Example:

ROLE_ACCOUNTANT
TASK_READ
TASK_WRITE
INVOICE_APPROVE
DOCUMENT_UPLOAD

Memory sentence:

Use roles for broad groups and authorities for fine-grained permissions.


46. Role Hierarchy Preview

Sometimes roles have hierarchy.

Example:

ADMIN includes MANAGER permissions.
MANAGER includes USER permissions.

Concept:

ROLE_ADMIN > ROLE_MANAGER > ROLE_USER

Spring Security supports role hierarchy, but it is an advanced topic.

For now, keep simple:

explicit roles and authorities

Memory sentence:

Role hierarchy can reduce repeated rules, but learn basic roles first.


47. Security Testing Preview

URL security test:

@WithMockUser(roles = "ADMIN")
@Test
void adminCanAccessAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}

Method security test:

@WithMockUser(roles = "USER")
@Test
void userCannotDeleteUser() {
assertThatThrownBy(() -> userService.deleteUser(1L))
.isInstanceOf(AccessDeniedException.class);
}

Memory sentence:

Security rules should be tested with allowed and denied users.


48. Common Exam Traps

Trap 1

Authentication and authorization are different.


Trap 2

401 and 403 are different.


Trap 3

Roles are authorities with ROLE_ prefix.


Trap 4

hasRole("ADMIN") checks ROLE_ADMIN.


Trap 5

hasAuthority("ADMIN") checks exactly ADMIN, not ROLE_ADMIN.


Trap 6

Do not use hasRole("ROLE_ADMIN").


Trap 7

Rule order matters in URL authorization.


Trap 8

Specific request matchers must come before anyRequest().


Trap 9

URL rules are coarse-grained.


Trap 10

Method security needs @EnableMethodSecurity.


Trap 11

@PreAuthorize checks before method execution.


Trap 12

@PostAuthorize checks after method execution.


Trap 13

Method security applies to Spring beans.


Trap 14

Self-invocation can bypass method security.


Trap 15

Never rely only on frontend authorization.


Trap 16

Never trust client-sent user ID for authenticated actions.


Trap 17

Data-level permissions often belong in service/domain logic.


Trap 18

Repository queries can include tenant/owner scope.


49. Real Exam Question: Authorization

Question:

What is authorization?

Answer:

Authorization is the process of deciding what an authenticated user is allowed to access or do.


50. Real Exam Question: Role

Question:

What is a role in Spring Security?

Answer:

A role is usually represented as an authority with the ROLE_ prefix, such as ROLE_ADMIN.


51. Real Exam Question: Authority

Question:

What is an authority?

Answer:

An authority is a permission granted to the user, such as ROLE_ADMIN, TASK_READ, or INVOICE_APPROVE.


52. Real Exam Question: hasRole

Question:

What does hasRole("ADMIN") check?

Answer:

It checks whether the current user has authority ROLE_ADMIN.


53. Real Exam Question: hasAuthority

Question:

What does hasAuthority("TASK_READ") check?

Answer:

It checks whether the current user has exactly the authority TASK_READ.


54. Real Exam Question: Rule Order

Question:

Why does authorization rule order matter?

Answer:

Rules are evaluated in order. Specific rules should be placed before general rules like anyRequest().


55. Real Exam Question: Method Security

Question:

How do I enable method security?

Answer:

Add @EnableMethodSecurity to a configuration class.


56. Real Exam Question: @PreAuthorize

Question:

What does @PreAuthorize do?

Answer:

@PreAuthorize checks an authorization expression before the method is invoked. If the expression fails, the method does not run.


57. Real Exam Question: URL vs Method Security

Question:

What is the difference between URL security and method security?

Answer:

URL security protects endpoints at the HTTP request level. Method security protects method calls, usually in the service layer, and can use method parameters and business-specific authorization expressions.


58. Real Exam Question: Data-Level Permission

Question:

What is data-level permission?

Answer:

Data-level permission means access depends on the specific resource or data, such as whether the current user belongs to the same tenant as the requested task.


59. Interview Answer

Question:

Explain roles and authorities in Spring Security.

Good answer:

Spring Security works with authorities. An authority is a permission, such as TASK_READ or ROLE_ADMIN. A role is normally represented as an authority with the ROLE_ prefix. For example, hasRole("ADMIN") checks for ROLE_ADMIN, while hasAuthority("TASK_READ") checks for exactly TASK_READ. I use roles for broad groups and authorities for fine-grained permissions.


60. Interview Answer

Question:

How do you design authorization in a Spring Boot REST API?

Good answer:

I use URL rules in SecurityFilterChain for broad endpoint access, such as public endpoints, admin endpoints, and authenticated APIs. Then I enforce business and data-level permissions in the service layer, either with explicit permission checks or method security like @PreAuthorize. For multi-tenant data, I make sure repository queries are scoped by tenant or owner so users cannot access data outside their permission boundary.


61. Interview Answer

Question:

What is @PreAuthorize?

Good answer:

@PreAuthorize is a method security annotation that checks an authorization expression before a method runs. It can check roles, authorities, method parameters, the current authentication, or call custom permission beans. To use it, method security must be enabled with @EnableMethodSecurity.


62. Interview Answer

Question:

Why are URL rules not enough?

Good answer:

URL rules are good for broad access, like requiring authentication or an admin role for a path. But many real permissions depend on the actual data, such as task ownership, tenant membership, invoice status, or who created a document. Those checks need to happen in service/domain logic or method security, not only in URL configuration.


63. Interview Answer

Question:

How do you handle tenant-level authorization?

Good answer:

I include the current user’s tenant information in the authenticated principal or load it from the database. Then service methods check that the requested resource belongs to the same tenant. When possible, I also scope repository queries directly, such as findByIdAndTenantId, so data outside the tenant is not loaded in the first place. For cross-tenant access attempts, the API may return 403 or 404 depending on the design.


64. Tiny Code Practice

Security config:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

Questions:

  1. Who can access /api/auth/login?
  2. Who can access /api/admin/users?
  3. What authority is needed for GET /api/tasks?
  4. What authority is needed for POST /api/tasks?
  5. What happens to all other requests?

Answers:

  1. Everyone.
  2. Users with role ADMIN, meaning authority ROLE_ADMIN.
  3. TASK_READ.
  4. TASK_WRITE.
  5. They require authentication.

65. Tiny Bug Practice 1

Problem:

.requestMatchers("/api/admin/**").hasRole("ROLE_ADMIN")

Question:

What is wrong?

Answer:

hasRole adds the ROLE_ prefix automatically. Use:

hasRole("ADMIN")

or:

hasAuthority("ROLE_ADMIN")

66. Tiny Bug Practice 2

Problem:

.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
.requestMatchers("/api/auth/**").permitAll()
)

Question:

What is wrong?

Answer:

anyRequest() is a general catch-all rule. It must come after specific rules.

Correct:

.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)

67. Tiny Bug Practice 3

Problem:

@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request.userId(), request.title());
}

Question:

Why is this dangerous?

Answer:

The client sends userId, so the client can pretend to be another user. Use the authenticated principal from Spring Security instead of trusting user identity from the request body.


68. Tiny Bug Practice 4

Problem:

@Service
public class TaskService {

public TaskDto findTask(Long id) {
return securedFindTask(id);
}

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto securedFindTask(Long id) {
return ...
}
}

Question:

What is the trap?

Answer:

This is self-invocation. A method inside the same class calls the secured method directly, possibly bypassing the Spring security proxy. Put @PreAuthorize on the public method called from outside, or move the secured method to another Spring bean.


Practice Questions and Answers

Question 1

What is authorization?

Answer:

Authorization is the process of deciding what an authenticated user is allowed to access or do.


Question 2

What is the difference between authentication and authorization?

Answer:

Authentication proves who the user is. Authorization checks what the user is allowed to do.


Question 3

What is an authority?

Answer:

An authority is a permission granted to a user, such as ROLE_ADMIN, TASK_READ, or INVOICE_APPROVE.


Question 4

What is a role?

Answer:

A role is usually a broad group of permissions and is commonly represented as an authority with the ROLE_ prefix.


Question 5

What does hasRole("ADMIN") check?

Answer:

It checks whether the user has authority ROLE_ADMIN.


Question 6

What does hasAuthority("TASK_READ") check?

Answer:

It checks whether the user has exactly the authority TASK_READ.


Question 7

Why is hasRole("ROLE_ADMIN") usually wrong?

Answer:

Because hasRole adds the ROLE_ prefix automatically. hasRole("ROLE_ADMIN") may effectively look for ROLE_ROLE_ADMIN.


Question 8

Why does URL rule order matter?

Answer:

Rules are evaluated in order. Specific rules must come before general catch-all rules like anyRequest().


Question 9

What does permitAll() mean?

Answer:

permitAll() means anyone can access, including unauthenticated users.


Question 10

What does authenticated() mean?

Answer:

authenticated() means the user must be logged in or otherwise authenticated.


Question 11

What is method security?

Answer:

Method security means applying authorization rules to method calls, often service methods.


Question 12

How do I enable method security?

Answer:

Use @EnableMethodSecurity on a configuration class.


Question 13

What does @PreAuthorize do?

Answer:

@PreAuthorize checks an authorization expression before the method runs.


Question 14

What does @PostAuthorize do?

Answer:

@PostAuthorize checks authorization after the method returns, often using the returned object.


Question 15

How can @PreAuthorize use method parameters?

Answer:

Use #parameterName in the expression, such as @PreAuthorize("#userId == authentication.principal.id").


Question 16

How can @PreAuthorize call a custom permission service?

Answer:

Reference the bean by name, for example @PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)").


Question 17

What is data-level permission?

Answer:

Data-level permission means access depends on the specific resource or data, such as ownership or tenant membership.


Question 18

Why are URL rules not enough for tenant security?

Answer:

URL rules can check broad access to endpoints, but they usually cannot know whether the requested client, task, invoice, or document belongs to the current user’s tenant.


Question 19

Why should I not trust client-sent user IDs?

Answer:

Because the client can manipulate request data and pretend to be another user. Use the authenticated principal instead.


Question 20

What is the self-invocation trap with method security?

Answer:

Self-invocation happens when one method in the same class calls another secured method directly. The call may bypass the Spring proxy, so method security may not apply.

Final Memory Sentences

  • Authorization checks permissions.
  • Authentication proves identity.
  • Authorities are permissions.
  • Roles are authorities with ROLE_ prefix.
  • hasRole("ADMIN") checks ROLE_ADMIN.
  • hasAuthority("TASK_READ") checks exactly TASK_READ.
  • Do not use hasRole("ROLE_ADMIN").
  • URL authorization protects endpoints.
  • Rule order matters.
  • Specific rules go before anyRequest().
  • URL security is coarse-grained.
  • Method security is fine-grained.
  • Enable method security with @EnableMethodSecurity.
  • @PreAuthorize checks before method execution.
  • @PostAuthorize checks after method execution.
  • @PreAuthorize can use method parameters.
  • @PreAuthorize can call custom permission beans.
  • Data-level permissions belong in service/domain logic.
  • Tenant security must check tenant ownership.
  • Never trust client-sent user identity.
  • Backend must enforce authorization even if frontend hides buttons.
  • Repository queries can include tenant or owner scope.
  • Method security works on Spring-managed beans.
  • Self-invocation can bypass method security.