Authentication is only half the battle. This project demonstrates how to differentiate between standard Users and Admins. It also shows how to "gracefully fail" by returning clean, professional JSON error messages when a user tries to access a resource they aren't authorized for.
Instead of just securing URLs in the config file, we use annotations directly on the Controller methods.
@PreAuthorize("hasRole('ADMIN')"): This is powerful because it checks the user's authority before the method even executes. If the check fails, anAccessDeniedExceptionis thrown.
By default, Spring Security returns a generic "Whitelabel Error Page" for 403 Forbidden errors. We implemented CustomAccessDeniedHandler to:
- Intercept authorization failures.
- Return a structured JSON response (ideal for modern frontend/mobile apps).
- Provide specific context like the requested path and a custom message.
In SecurityConfig.java, we define two users:
- Ejaz: Has the role
USER. - Amiya: Has the roles
ADMINandUSER. This allows Amiya to access everything, while Ejaz is restricted to his own profile.
Contains three tiers of access:
- Public:
/auth/welcome(No login required). - Private (User):
/auth/user/userProfile(RequiresUSERrole). - Restricted (Admin):
/auth/admin/adminProfile(RequiresADMINrole).
Uses @ControllerAdvice to catch AccessDeniedException across the entire application. This ensures that even if an error occurs outside of the standard filter chain, the user receives a consistent response.
| User | Endpoint | Expected Result | Why? |
|---|---|---|---|
| Anonymous | /auth/welcome |
200 OK | Permitted to all. |
| Ejaz (User) | /auth/user/userProfile |
200 OK | Has required USER role. |
| Ejaz (User) | /auth/admin/adminProfile |
403 Forbidden | Custom JSON error triggered. |
| Amiya (Admin) | /auth/admin/adminProfile |
200 OK | Has required ADMIN role. |
AccessDeniedHandler:
- Triggered when a user is authenticated but lacks the required role/authority.
- Example: A logged-in user with role USER tries to access /admin.
Default Behavior:
- Spring Security normally shows a generic error page (HTML).
- Custom Behavior (this class):
- Returns a JSON response instead of HTML.
- Makes it easier for front-end apps (React, Angular, Vue, etc.) to handle errors.
- Response Example:
{
"status": 403,
"error": "Forbidden",
"message": "You do not have permission to access this admin's resources",
"path": "/admin/dashboard"
} - @EnableMethodSecurity: Allows method-level annotations like
@PreAuthorize("hasRole('ADMIN')"). - CustomAccessDeniedHandler: Returns JSON instead of default HTML error page when access is denied.
Authorization Rules:
/auth/welcome→ public./auth/user/**→ requires USER role./auth/admin/**→ requires ADMIN role.- Form Login: Redirects to
/auth/welcomeafter login. - HTTP Basic: Allows API clients to authenticate with headers.
- InMemoryUserDetailsManager: Stores demo users (Amiya as ADMIN+USER, Ejaz as USER).
- @ControllerAdvice: Centralizes exception handling → applies to all controllers.
- @ExceptionHandler(AccessDeniedException.class): Handles authorization failures globally.
- AccessDeniedException: Thrown when a logged-in user tries to access a resource they don’t have permission for.
- ResponseEntity: Lets you return both a body (JSON message) and a status code.
- Map.of(...): Creates a simple JSON-like response →
{ "message": "Access Denied: You are not an Admin!" }- @RestController: Exposes endpoints that return plain text/JSON responses.
- @RequestMapping("/auth"): Base path → all endpoints start with /auth.
- @PreAuthorize: Method-level security → checks roles before executing the method.
Role-based Access:
/auth/welcome→ public (no login required)./auth/user/userProfile→ requires USER role./auth/admin/adminProfile→ requires ADMIN role.
- @SpringBootApplication: Marks this as the main Spring Boot app → auto-configures everything.
- SpringApplication.run(): Starts the app → loads beans, controllers, and security configuration.
Integration:
- Loads your SecurityConfig (role-based rules + custom access denied handler).
- Loads your UserController (endpoints for welcome, user, and admin).
- Connects exception handling (GlobalExceptionHandler).
- Run Application: Start
SpringSecurityAuthenticationAuthorizationDemoApplication. - Public Test: Open
http://localhost:8080/auth/welcome. No login needed. - Authorization Test (Failure):
- Login as Ejaz (Password:
456). - Try to access
http://localhost:8080/auth/admin/adminProfile. - Result: You will see the custom JSON message: "You do not have permission to access this admin's resources".
- Login as Ejaz (Password:
- Authorization Test (Success):
- Login as Amiya (Password:
123). - Access the Admin profile. Success!
- Login as Amiya (Password: