Intermediate · Secure
Enforce Permissions, CORS, and CSRF Correctly
Separate origin policy, cross-site request forgery defenses, URL rules, and method-level authorization.
Primary source: Spring Security 6.5 Reference Architecture
Lesson 3 of 5
1d streakThe Architectural Question
Confused 401/403 responses, blanket CORS wildcards, and CSRF disabled by habit each trade correctness for a moment of convenience.
What this guide proves
- Return 401 for missing or invalid authentication and 403 for insufficient authority
- Keep CSRF enabled for browser flows using automatically attached credentials
- Model authorities around actions, not UI labels
RBAC vs ABAC & Declarative Method Security with @PreAuthorize
Securing endpoints solely at the HTTP URL level is fragile. Modern applications pair URL authorization with declarative Method Security evaluated directly on service and controller methods using SpEL (Spring Expression Language).
package com.example.order;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// Role-based authorization:
@PreAuthorize("hasRole('ADMIN')")
public void purgeCancelledOrders() { ... }
// Authority / Permission-based authorization:
@PreAuthorize("hasAuthority('orders:approve')")
public void approveOrder(String orderId) { ... }
// Attribute-Based Access Control (ABAC): Inspects method argument against principal:
@PreAuthorize("#order.customerId == authentication.name or hasRole('ADMIN')")
public void cancelOrder(Order order) { ... }
}Enable Method Security in Spring Boot 3+
@EnableMethodSecurity to any @Configuration class. By default, this enables @PreAuthorize and @PostAuthorize while disabling legacy @Secured and jsr250 annotations.Active Recall Checkpoint
What is the difference between hasRole("ADMIN") and hasAuthority("ADMIN") in Spring Security?
Hint: What prefix does hasRole look for automatically?
Show answer & explanation
hasRole("ADMIN") automatically prepends the prefix ROLE_, looking for the granted authority ROLE_ADMIN. hasAuthority("ADMIN") looks for the exact string "ADMIN" without modification.
CORS Protocol Mechanics: Preflights, Headers & Safe Configuration
Cross-Origin Resource Sharing (CORS) is a browser security mechanism that restricts web pages from making HTTP requests to a different domain, port, or protocol than the origin serving the web page. CORS is enforced by the browser, NOT by the server.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com", "https://admin.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Correlation-Id"));
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1)); // Cache preflight response in browser
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}The Wildcard + Credentials Security Prohibition
Access-Control-Allow-Origin: * when Access-Control-Allow-Credentials: true. Browsers will reject the response immediately to prevent malicious third-party sites from reading authenticated session cookies.Active Recall Checkpoint
Why does a browser send an HTTP OPTIONS preflight request before sending a POST request with an Authorization header?
Hint: Is Authorization considered a CORS-safelisted request header?
Show answer & explanation
No. Any request with non-safelisted headers (such as Authorization or Content-Type: application/json) triggers a preflight OPTIONS request so the browser can verify whether the server permits cross-origin access before sending the actual data.
CSRF Protection: Cookie Token Repository vs Stateless APIs
Cross-Site Request Forgery (CSRF) occurs when a malicious website tricks a user's browser into executing unwanted actions on an application where the user is currently authenticated via cookies.
Architectural Boundary · Do Not Confuse
Ambient Credential Authentication (Cookies, Basic Auth, Client Certificates): Vulnerable to CSRF because browsers automatically attach ambient credentials to cross-origin requests. CSRF protection MUST BE ENABLED using the synchronizer token pattern (such as CookieCsrfTokenRepository).
Explicit Credential APIs (Header-Only Bearer Tokens): Immune to CSRF only if the application accepts credentials exclusively via non-ambient HTTP request headers (like Authorization: Bearer) that browsers never attach automatically. Setting SessionCreationPolicy.STATELESS does not alone justify disabling CSRF if cookie or basic credentials remain accepted.
CSRF risk is dictated by how credentials are sent by browsers, not whether the server stores sessions in memory. Disable CSRF only when credentials require explicit client-side JavaScript attachment that malicious third-party pages cannot trigger automatically.
Active Recall Checkpoint
When building a single-page application (React/Angular) authenticated via session cookies, how should Spring Security expose the CSRF token?
Hint: How does JavaScript read the cookie?
Show answer & explanation
Configure CookieCsrfTokenRepository.withHttpOnlyFalse(). This allows JavaScript to read the XSRF-TOKEN cookie value and copy it into the X-XSRF-TOKEN HTTP header on mutating requests.
Deliberate Failure Lab: Diagnosing CORS Preflight 403 Forbidden
One of the most frequent frontend-backend integration crashes is receiving 403 Forbidden on an OPTIONS preflight request from a web browser.
Access to XMLHttpRequest at 'https://api.example.com/api/orders' from origin 'https://app.example.com'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
It does not have HTTP ok status (Status 403 Forbidden).Root Cause Analysis
OPTIONS request carries no Authorization header. If http.cors() is not enabled in SecurityFilterChain, Spring Security intercepts the OPTIONS request in AuthorizationFilter and rejects it with 403 Forbidden before CORS processing ever runs!The Fix
Active Recall Checkpoint
Why does an HTTP preflight OPTIONS request never contain an Authorization header?
Hint: Does the browser send user credentials during preflight checks?
Show answer & explanation
The browser intentionally omits credentials and authorization headers during the preflight probe to verify server CORS policies anonymously before committing user credentials.
Runtime Architecture & Flow
Decision canvas
Evaluate the choice across three constraints
These are comparison criteria, not sequential steps. A strong answer states which constraint dominates in the current system.
Return 401 for missing or invalid authentication and 403 for insufficient authority
Ask how each option changes this guarantee, its operational cost, and its failure mode.
Keep CSRF enabled for browser flows using automatically attached credentials
Ask how each option changes this guarantee, its operational cost, and its failure mode.
Model authorities around actions, not UI labels
Ask how each option changes this guarantee, its operational cost, and its failure mode.
Decision prompt: Lock one endpoint behind `orders:approve` and test it with three principals.
Working Implementation
Code Walkthrough
Lock one endpoint behind `orders:approve` and test it with three principals.
package com.example.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Service;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableMethodSecurity
public class MethodSecurityCorsLab {
@Bean
SecurityFilterChain configureSecurity(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // Disabled for stateless REST bearer APIs
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://dashboard.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Service
public static class OrderApprovalService {
@PreAuthorize("hasAuthority('orders:approve')")
public void approveOrder(long orderId) {
// Only principals with orders:approve authority can execute this method
}
}
}Synthesis & Production Verification
Production Acceptance Criteria
- Differentiate 401 from 403 in tests with distinct principals
- Keep CSRF enabled for cookie flows and document the reasoning
- Gate one action behind a fine-grained authority with @PreAuthorize
Key Takeaway
Pairing HTTP URL matchers with declarative method security (@PreAuthorize) provides defense-in-depth, while CORS and CSRF configurations must strictly match client authentication semantics.
Architectural & Technical Review
Core Interview Questions
Q01What is the architectural difference between URL authorization and method security in Spring Security?Show answer
URL authorization filters requests at the HTTP boundary before reaching controllers. Method security (@PreAuthorize, @PostAuthorize) enforces fine-grained role, permission, and ABAC rules directly at service methods.
Q02How does CORS preflight OPTIONS request handling interact with Spring Security filter ordering?Show answer
Browsers send preflight OPTIONS requests without credentials. Spring Security must configure .cors() so CorsFilter runs before AuthorizationFilter, preventing preflights from returning 403 Forbidden.
Q03When is it safe to disable CSRF protection in a Spring Boot application?Show answer
CSRF can be safely disabled for stateless REST APIs authenticated purely via Authorization: Bearer headers. It must remain enabled for applications authenticated via browser cookies.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Setting Access-Control-Allow-Origin: * with AllowCredentials: true — Prohibited by W3C CORS specification; browsers will reject cross-origin responses that combine wildcard origins with credentials.
- Pitfall: Blocking preflight OPTIONS requests with 403 Forbidden due to filter order — Always enable .cors() in the SecurityFilterChain so CorsFilter inspects preflight requests before authentication checks.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources