Sprint 2 Refinement - User Management
Overview
Technology Stack
Backend (Spring Boot)
- Framework: Spring Boot 3.x
- Persistence: Spring Data JPA + PostgreSQL/MySQL
- Validation: Bean Validation (Hibernate Validator)
- Security: Spring Security + JWT
- Documentation: OpenAPI 3 (Swagger)
- Logs: Logback/SLF4J
Frontend (Next.js)
- Framework: Next.js 14+ (App Router)
- UI Components: React + TypeScript
- Styling: Tailwind CSS or Material-UI
- State Management: React Query/TanStack Query
- Forms: React Hook Form + Zod validation
- HTTP Client: Axios or Fetch API
Data Structure
Main Entities
- User: Base entity for collaborators and PDMs
- UserType: ENUM (COLLABORATOR, PDM)
- UserStatus: ENUM (ACTIVE, INACTIVE)
Relationships
- User → User: Self-referencing relationship (PDM → Collaborator)
REST APIs
Main Endpoints
GET /api/users- Paginated list of usersPOST /api/users- User creationGET /api/users/{id}- User detailsPUT /api/users/{id}- User updateDELETE /api/users/{id}- User deactivationGET /api/users/pdms- List of available PDMs
Patterns
- DTOs: Request/Response separate from entities
- Validation: Business validation in the service layer
- Exception Handling: Global exception handler
- Pagination: Spring Data Pageable
Frontend Components
Pages/Routes
/admin/users- List of users/admin/users/new- User registration/admin/users/[id]/edit- User editing
Main Components
- UserList: Table with pagination and actions
- UserForm: Reusable form (create/edit)
- UserTypeSelector: Selector with conditional logic
- PDMSelector: Dropdown of available PDMs
- ConfirmDialog: Confirmation modal for deletion
Understanding Spring Security
Spring Security is a powerful and highly customizable framework that provides authentication and access control for Java applications, especially those built with the Spring Framework. It supports various authentication methods, including form-based authentication, basic authentication, OAuth2, and JWT (JSON Web Tokens), allowing developers to implement security flexibly and effectively. Spring Security also enables the definition of authorization rules through annotations, such as @PreAuthorize, which facilitate access control to methods and resources based on user permissions. Additionally, the framework integrates seamlessly with Spring's security model, allowing for session management and protection against common vulnerabilities like CSRF (Cross-Site Request Forgery) and brute-force attacks, making it a popular choice for securing web applications and RESTful APIs.
Explanation:
- Client: The entry point that makes an HTTP request.
- OncePerRequestFilter: A filter that is invoked for each request, ensuring that authentication and authorization are processed.
- Authentication: The object containing the authenticated user's information.
- SecurityContextHolder: A holder that stores the security context of the current request.
- Authority Check: The process of checking if the authenticated user has the necessary authority.
- @PreAuthorize: Annotation that protects methods based on the user's authorities.
- Request Processed: If the user has the authority, the request is processed normally.
- Access Denied: If the user does not have the authority, an access denied error is returned.
- Security Configuration: Where Spring Security settings are defined, including session management.
Notes:
- The configuration
sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))indicates that the application will not maintain sessions and each request is independent, which is common in RESTful APIs.
Protecting Endpoints with HasType
The HasType component is a custom annotation that combines the functionality of Spring Security with the flexibility of custom annotations, allowing methods or classes to be protected based on the user's authorities (permissions). When applied to a method or class, this annotation uses the @PreAuthorize expression to check if the authenticated user has the authority specified in the value parameter. This provides more granular access control, allowing developers to easily define which roles or permissions are required to access certain application functionalities, simplifying the implementation of security rules and making the code more readable and organized. Thus, HasType can be used to protect endpoints or services, ensuring that only users with the appropriate permissions can execute them.
Note
For more granular access management, use the @PreAuthorize annotation, which allows for more advanced expressions.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/resource")
public class ResourceController {
@GetMapping("/secure-data")
@HasType("ADMIN") // Only users with the ROLE_ADMIN authority can access this method
public ResponseEntity<String> getSecureData() {
String secureData = "This is secure data that only administrators can access.";
return ResponseEntity.ok(secureData); // Returns the secure data
}
}
Protecting Next.js Routes with Middleware
Middleware-based protection in Next.js is an approach that allows for the centralized implementation of authentication and authorization logic before requests reach the specific routes of the application. Middleware can be used to intercept requests and check if a user is authenticated or has the necessary permissions to access certain pages or resources. This provides an additional layer of security, ensuring that only authorized users can access sensitive information or restricted actions within the application. Furthermore, using middleware improves code organization, as the security logic can be separated from business functions.
Example:
// middleware.js
import { NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
// Function to check if the user is ADMIN
export async function middleware(req) {
const token = req.cookies.get('token')?.value; // Assuming the JWT token is stored in cookies
if (!token) {
// If there is no token, redirect to the login page
return NextResponse.redirect(new URL('/login', req.url));
}
const decoded = jwt.decode(token); // Decodes the token without verifying the signature
// Checks if the user type is ADMIN
if (!decoded || decoded.type !== 'ADMIN') {
return NextResponse.redirect(new URL('/unauthorized', req.url)); // Redirects to the unauthorized page
}
// If the type is ADMIN, allows access to the route
return NextResponse.next();
}
// Configuring the middleware for the /users routes
export const config = {
matcher: ['/users/:path*'],
};