From 8fb5989bae3f2afb07b965da326e2e1bf7528f95 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Mon, 13 Jul 2026 10:30:29 +0000 Subject: [PATCH] =?UTF-8?q?=EC=9D=B8=EC=A6=9D/=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=97=85=EB=AC=B4=20=EA=B7=9C=EC=B9=99=EA=B3=BC=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EB=B6=88=EB=B3=80=EC=8B=9D=20=EC=B6=94=EC=B6=9C=20?= =?UTF-8?q?(codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-AUTH-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth-migration/INVARIANTS.md | 163 +++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 auth-migration/INVARIANTS.md diff --git a/auth-migration/INVARIANTS.md b/auth-migration/INVARIANTS.md new file mode 100644 index 0000000..1edc852 --- /dev/null +++ b/auth-migration/INVARIANTS.md @@ -0,0 +1,163 @@ +# Authentication & Authorization Transition Invariants + +## 1. Authentication Rules + +### 1.1 Login Flow + +| Rule ID | Description | Invariant | Error Code | +|---------|-------------|-----------|------------| +| AUTH-001 | Username/password validation | Credentials must match stored hash; empty credentials rejected | `AUTH_001` | +| AUTH-002 | Account lockout | 5 failed attempts → 15min lockout | `AUTH_002` | +| AUTH-003 | Session creation | Valid JWT issued with 30min TTL | `AUTH_003` | +| AUTH-004 | Password hashing | BCrypt with cost factor 12 | `AUTH_004` | + +### 1.2 Token Management + +| Rule ID | Description | Invariant | Error Code | +|---------|-------------|-----------|------------| +| AUTH-010 | JWT structure | `{sub, roles[], exp, iat}` claims required | `AUTH_010` | +| AUTH-011 | Token refresh | Refresh token valid for 7 days, one-time use | `AUTH_011` | +| AUTH-012 | Token revocation | Logout invalidates token immediately | `AUTH_012` | + +### 1.3 MFA Rules + +| Rule ID | Description | Invariant | Error Code | +|---------|-------------|-----------|------------| +| AUTH-020 | MFA required | TOTP 6-digit code, 30s window, ±1 drift | `AUTH_020` | +| AUTH-021 | MFA bypass | Only for TRUSTED_DEVICE users | `AUTH_021` | + +## 2. Authorization Rules + +### 2.1 Role Hierarchy + +| Rule ID | Description | Invariant | Error Code | +|---------|-------------|-----------|------------| +| AUTH-030 | Role precedence | ADMIN > MANAGER > USER > GUEST | `AUTH_030` | +| AUTH-031 | Role assignment | Only ADMIN can assign roles | `AUTH_031` | +| AUTH-032 | Self-demotion | ADMIN cannot demote own account | `AUTH_032` | + +### 2.2 Permission Boundaries + +| Rule ID | Description | Invariant | Error Code | +|---------|-------------|-----------|------------| +| AUTH-040 | Resource ownership | Users can only modify own resources | `AUTH_040` | +| AUTH-041 | Cross-tenant access | No cross-tenant data access | `AUTH_041` | +| AUTH-042 | Admin override | ADMIN bypasses ownership check | `AUTH_042` | + +### 2.3 API Endpoint Permissions + +| Rule ID | Endpoint Pattern | Required Role | Invariant | +|---------|------------------|---------------|-----------| +| AUTH-050 | `POST /auth/login` | GUEST | Public endpoint | +| AUTH-051 | `POST /auth/logout` | USER+ | Authenticated | +| AUTH-052 | `GET /admin/*` | ADMIN | Admin only | +| AUTH-053 | `POST /users/*` | MANAGER+ | Manager+ only | +| AUTH-054 | `GET /reports/*` | USER+ | Authenticated | + +## 3. Error Response Format + +### 3.1 Standard Error Structure + +```json +{ + "code": "AUTH_XXX", + "message": "Human-readable message", + "timestamp": "ISO-8601", + "path": "/original/request/path" +} +``` + +### 3.2 HTTP Status Mapping + +| HTTP Status | Condition | +|-------------|-----------| +| 400 | Invalid request format | +| 401 | Missing/invalid credentials | +| 403 | Insufficient permissions | +| 404 | Resource not found | +| 423 | Account locked | +| 429 | Rate limit exceeded | +| 500 | Internal server error | + +## 4. Security Invariants (Must Not Change) + +| Priority | Invariant | Rationale | +|----------|-----------|-----------| +| CRITICAL | Passwords never logged | PII protection | +| CRITICAL | Tokens never in URL query | Prevent log leakage | +| CRITICAL | Session fixation prevention | CSRF protection | +| HIGH | Rate limiting per IP | DoS prevention | +| HIGH | Audit logging on auth events | Compliance | + +## 5. Spring Migration Checklist + +- [ ] SecurityConfig with BCryptPasswordEncoder bean +- [ ] JwtAuthenticationFilter ordering (before UsernamePasswordAuthenticationFilter) +- [ ] @EnableMethodSecurity for @PreAuthorize annotations +- [ ] Custom AuthenticationEntryPoint for error responses +- [ ] Custom AccessDeniedHandler for 403 responses +- [ ] SessionManagement with fixed session fixation protection +- [ ] CORS configuration aligned with existing origins + +## 6. Implementation Reference + +### 6.1 SecurityConfig Bean Configuration + +```java +@Bean +public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(12); // AUTH-004 +} + +@Bean +public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .csrf(csrf -> csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + .ignoringRequestMatchers("/auth/login", "/auth/refresh") + ) + .sessionManagement(s -> s + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .sessionFixation().migrateSession() + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/auth/login", "/auth/refresh").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .requestMatchers("/users/**").hasAnyRole("MANAGER", "ADMIN") + .requestMatchers("/reports/**").authenticated() + .anyRequest().authenticated() + ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(new CustomAuthEntryPoint()) + .accessDeniedHandler(new CustomAccessDeniedHandler()) + ) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .build(); +} +``` + +### 6.2 JWT Claims Structure + +```java +public record JwtClaims( + String subject, // username + List roles, // ["ADMIN", "USER"] + Date issuedAt, + Date expiration // 30min for access, 7days for refresh +) {} +``` + +### 6.3 Lockout Logic + +```java +private static final int MAX_FAILED_ATTEMPTS = 5; +private static final long LOCKOUT_DURATION_MS = 15 * 60 * 1000; +``` + +### 6.4 Role Hierarchy Levels + +```java +private static final List ROLE_HIERARCHY = + List.of("ADMIN", "MANAGER", "USER", "GUEST"); +// Lower index = higher privilege +```