Skip to content

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 users
  • POST /api/users - User creation
  • GET /api/users/{id} - User details
  • PUT /api/users/{id} - User update
  • DELETE /api/users/{id} - User deactivation
  • GET /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

mmd

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.

mmd

Explanation:

  1. Client: The entry point that makes an HTTP request.
  2. OncePerRequestFilter: A filter that is invoked for each request, ensuring that authentication and authorization are processed.
  3. Authentication: The object containing the authenticated user's information.
  4. SecurityContextHolder: A holder that stores the security context of the current request.
  5. Authority Check: The process of checking if the authenticated user has the necessary authority.
  6. @PreAuthorize: Annotation that protects methods based on the user's authorities.
  7. Request Processed: If the user has the authority, the request is processed normally.
  8. Access Denied: If the user does not have the authority, an access denied error is returned.
  9. 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.

mmd

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*'],
};