NGINX Auth Request Integration with Java Backend: Complete Guide

Introduction

NGINX Auth Request module enables external authentication for web applications by delegating authentication decisions to a backend service. This pattern is ideal for securing static content, APIs, and microservices where you want centralized authentication logic. This guide explores how to implement an NGINX Auth Request backend in Java.


Article: Implementing NGINX Auth Request Backend in Java

NGINX Auth Request allows you to protect resources by making a subrequest to an authentication service. The Java backend decides whether to allow access based on the request headers, cookies, or other criteria, providing a flexible and centralized authentication mechanism.

1. NGINX Auth Request Architecture

Flow Overview:

Client Request → NGINX → Auth Subrequest → Java Backend
↓
Java Response (2xx=Allow, 4xx/5xx=Deny) → NGINX → Client

Key Components:

  • NGINX - Reverse proxy with auth_request module
  • Java Auth Service - Authentication decision endpoint
  • Headers - Forwarded request information
  • Cookies - Session management
  • Response Codes - Allow/Deny decisions

2. Maven Dependencies

pom.xml:

<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<jwt.version>0.11.5</jwt.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>

3. Application Configuration

application.yml:

server:
port: 8080
servlet:
context-path: /auth
spring:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
database: 0
app:
auth:
# JWT Configuration
jwt:
secret: ${JWT_SECRET:mySuperSecretKeyForJWTGenerationInProduction}
expiration-ms: 3600000  # 1 hour
issuer: nginx-auth-service
# Session Configuration
session:
timeout-minutes: 30
cookie-name: AUTH_SESSION
secure-cookie: true
# Rate Limiting
rate-limit:
enabled: true
requests-per-minute: 60
burst-capacity: 100
# NGINX Headers
nginx:
original-uri-header: X-Original-URI
original-method-header: X-Original-Method
user-header: X-User
roles-header: X-Roles
forwarded-for-header: X-Forwarded-For
real-ip-header: X-Real-IP
logging:
level:
com.myapp.nginx.auth: DEBUG

4. NGINX Configuration

nginx.conf:

http {
# Upstream auth service
upstream auth_backend {
server localhost:8080;
}
# Upstream protected application
upstream app_backend {
server localhost:3000;
}
server {
listen 80;
server_name myapp.com;
# Protected location - requires authentication
location /protected/ {
# Auth request configuration
auth_request /auth-verify;
auth_request_set $user $upstream_http_x_user;
auth_request_set $roles $upstream_http_x_roles;
auth_request_set $auth_status $upstream_status;
# Pass headers to backend application
proxy_set_header X-User $user;
proxy_set_header X-Roles $roles;
proxy_set_header X-Auth-Status $auth_status;
# Forward to application backend
proxy_pass http://app_backend;
# Error handling
error_page 401 = @error401;
error_page 403 = @error403;
}
# Static files - no authentication
location /public/ {
alias /var/www/public/;
expires 1h;
}
# API endpoints - requires authentication
location /api/ {
auth_request /auth-verify;
auth_request_set $user $upstream_http_x_user;
auth_request_set $roles $upstream_http_x_roles;
proxy_set_header X-User $user;
proxy_set_header X-Roles $roles;
proxy_pass http://app_backend;
error_page 401 = @error401;
error_page 403 = @error403;
}
# Authentication verification endpoint (internal)
location = /auth-verify {
internal;
proxy_pass http://auth_backend/auth/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Cookie $http_cookie;
}
# Login endpoint (public)
location /auth/login {
proxy_pass http://auth_backend/auth/login;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Logout endpoint (public)
location /auth/logout {
proxy_pass http://auth_backend/auth/logout;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Error pages
location @error401 {
return 302 /auth/login?return_url=$request_uri;
}
location @error403 {
return 403 "Access Denied";
}
location @error500 {
return 500 "Authentication Service Error";
}
}
}

5. Domain Models

Authentication Request/Response:

package com.myapp.nginx.auth.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.Set;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AuthRequest {
@NotBlank
private String originalUri;
@NotBlank
private String originalMethod;
private String realIp;
private String forwardedFor;
private String scheme;
private String cookie;
private String userAgent;
// Constructors
public AuthRequest() {}
public AuthRequest(String originalUri, String originalMethod) {
this.originalUri = originalUri;
this.originalMethod = originalMethod;
}
// Getters and Setters
public String getOriginalUri() { return originalUri; }
public void setOriginalUri(String originalUri) { this.originalUri = originalUri; }
public String getOriginalMethod() { return originalMethod; }
public void setOriginalMethod(String originalMethod) { this.originalMethod = originalMethod; }
public String getRealIp() { return realIp; }
public void setRealIp(String realIp) { this.realIp = realIp; }
public String getForwardedFor() { return forwardedFor; }
public void setForwardedFor(String forwardedFor) { this.forwardedFor = forwardedFor; }
public String getScheme() { return scheme; }
public void setScheme(String scheme) { this.scheme = scheme; }
public String getCookie() { return cookie; }
public void setCookie(String cookie) { this.cookie = cookie; }
public String getUserAgent() { return userAgent; }
public void setUserAgent(String userAgent) { this.userAgent = userAgent; }
}
public class AuthResponse {
private boolean authenticated;
private String userId;
private Set<String> roles;
private String message;
private LocalDateTime timestamp;
// Constructors
public AuthResponse() {
this.timestamp = LocalDateTime.now();
}
public AuthResponse(boolean authenticated, String userId, Set<String> roles) {
this();
this.authenticated = authenticated;
this.userId = userId;
this.roles = roles;
}
// Getters and Setters
public boolean isAuthenticated() { return authenticated; }
public void setAuthenticated(boolean authenticated) { this.authenticated = authenticated; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public Set<String> getRoles() { return roles; }
public void setRoles(Set<String> roles) { this.roles = roles; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}
public class LoginRequest {
@NotBlank
private String username;
@NotBlank
private String password;
private boolean rememberMe = false;
// Getters and Setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public boolean isRememberMe() { return rememberMe; }
public void setRememberMe(boolean rememberMe) { this.rememberMe = rememberMe; }
}
public class LoginResponse {
private boolean success;
private String token;
private String userId;
private Set<String> roles;
private String message;
private LocalDateTime expiresAt;
// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public Set<String> getRoles() { return roles; }
public void setRoles(Set<String> roles) { this.roles = roles; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getExpiresAt() { return expiresAt; }
public void setExpiresAt(LocalDateTime expiresAt) { this.expiresAt = expiresAt; }
}

6. JWT Token Service

JWT Token Service:

package com.myapp.nginx.auth.service;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;
import java.util.Set;
@Service
public class JwtTokenService {
private static final Logger logger = LoggerFactory.getLogger(JwtTokenService.class);
@Value("${app.auth.jwt.secret}")
private String jwtSecret;
@Value("${app.auth.jwt.expiration-ms}")
private long jwtExpirationMs;
@Value("${app.auth.jwt.issuer}")
private String jwtIssuer;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
public String generateToken(String userId, Set<String> roles, Map<String, Object> additionalClaims) {
Instant now = Instant.now();
Instant expiration = now.plusMillis(jwtExpirationMs);
JwtBuilder builder = Jwts.builder()
.setSubject(userId)
.setIssuer(jwtIssuer)
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(expiration))
.claim("roles", roles)
.signWith(getSigningKey(), SignatureAlgorithm.HS512);
if (additionalClaims != null) {
builder.setClaims(additionalClaims);
}
return builder.compact();
}
public Jws<Claims> validateToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
} catch (ExpiredJwtException e) {
logger.warn("JWT token expired: {}", e.getMessage());
throw new TokenValidationException("Token expired", e);
} catch (MalformedJwtException e) {
logger.warn("Invalid JWT token: {}", e.getMessage());
throw new TokenValidationException("Invalid token", e);
} catch (Exception e) {
logger.warn("Token validation failed: {}", e.getMessage());
throw new TokenValidationException("Token validation failed", e);
}
}
public String getUserIdFromToken(String token) {
try {
Jws<Claims> claims = validateToken(token);
return claims.getBody().getSubject();
} catch (TokenValidationException e) {
return null;
}
}
@SuppressWarnings("unchecked")
public Set<String> getRolesFromToken(String token) {
try {
Jws<Claims> claims = validateToken(token);
return (Set<String>) claims.getBody().get("roles");
} catch (TokenValidationException e) {
return Set.of();
}
}
public LocalDateTime getExpirationDateFromToken(String token) {
try {
Jws<Claims> claims = validateToken(token);
Date expiration = claims.getBody().getExpiration();
return LocalDateTime.ofInstant(expiration.toInstant(), ZoneId.systemDefault());
} catch (TokenValidationException e) {
return null;
}
}
public boolean isTokenValid(String token) {
try {
validateToken(token);
return true;
} catch (TokenValidationException e) {
return false;
}
}
public static class TokenValidationException extends RuntimeException {
public TokenValidationException(String message) {
super(message);
}
public TokenValidationException(String message, Throwable cause) {
super(message, cause);
}
}
}

7. Session Management Service

Session Service:

package com.myapp.nginx.auth.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
public class SessionService {
private static final Logger logger = LoggerFactory.getLogger(SessionService.class);
private final RedisTemplate<String, Object> redisTemplate;
private final JwtTokenService jwtTokenService;
@Value("${app.auth.session.timeout-minutes:30}")
private int sessionTimeoutMinutes;
@Value("${app.auth.session.cookie-name:AUTH_SESSION}")
private String sessionCookieName;
public SessionService(RedisTemplate<String, Object> redisTemplate, 
JwtTokenService jwtTokenService) {
this.redisTemplate = redisTemplate;
this.jwtTokenService = jwtTokenService;
}
public void createSession(String sessionId, String userId, Set<String> roles) {
String key = getSessionKey(sessionId);
SessionData sessionData = new SessionData();
sessionData.setUserId(userId);
sessionData.setRoles(roles);
sessionData.setCreatedAt(LocalDateTime.now());
sessionData.setLastAccessedAt(LocalDateTime.now());
redisTemplate.opsForValue().set(key, sessionData, sessionTimeoutMinutes, TimeUnit.MINUTES);
logger.debug("Created session: {} for user: {}", sessionId, userId);
}
public SessionData getSession(String sessionId) {
String key = getSessionKey(sessionId);
SessionData sessionData = (SessionData) redisTemplate.opsForValue().get(key);
if (sessionData != null) {
// Update last accessed time
sessionData.setLastAccessedAt(LocalDateTime.now());
redisTemplate.opsForValue().set(key, sessionData, sessionTimeoutMinutes, TimeUnit.MINUTES);
}
return sessionData;
}
public void updateSession(String sessionId, SessionData sessionData) {
String key = getSessionKey(sessionId);
sessionData.setLastAccessedAt(LocalDateTime.now());
redisTemplate.opsForValue().set(key, sessionData, sessionTimeoutMinutes, TimeUnit.MINUTES);
}
public void deleteSession(String sessionId) {
String key = getSessionKey(sessionId);
redisTemplate.delete(key);
logger.debug("Deleted session: {}", sessionId);
}
public boolean isValidSession(String sessionId) {
return getSession(sessionId) != null;
}
public void extendSession(String sessionId) {
SessionData sessionData = getSession(sessionId);
if (sessionData != null) {
updateSession(sessionId, sessionData);
}
}
public String extractSessionIdFromCookie(String cookieHeader) {
if (cookieHeader == null) {
return null;
}
String[] cookies = cookieHeader.split(";");
for (String cookie : cookies) {
String[] parts = cookie.trim().split("=");
if (parts.length == 2 && sessionCookieName.equals(parts[0].trim())) {
return parts[1].trim();
}
}
return null;
}
public String extractTokenFromHeader(String authHeader) {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null;
}
private String getSessionKey(String sessionId) {
return "session:" + sessionId;
}
public static class SessionData {
private String userId;
private Set<String> roles;
private LocalDateTime createdAt;
private LocalDateTime lastAccessedAt;
// Getters and Setters
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public Set<String> getRoles() { return roles; }
public void setRoles(Set<String> roles) { this.roles = roles; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getLastAccessedAt() { return lastAccessedAt; }
public void setLastAccessedAt(LocalDateTime lastAccessedAt) { this.lastAccessedAt = lastAccessedAt; }
}
}

8. Authentication Service

Authentication Service:

package com.myapp.nginx.auth.service;
import com.myapp.nginx.auth.model.AuthRequest;
import com.myapp.nginx.auth.model.AuthResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class AuthenticationService {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class);
private final SessionService sessionService;
private final JwtTokenService jwtTokenService;
private final UserService userService;
public AuthenticationService(SessionService sessionService,
JwtTokenService jwtTokenService,
UserService userService) {
this.sessionService = sessionService;
this.jwtTokenService = jwtTokenService;
this.userService = userService;
}
public AuthResponse authenticateRequest(AuthRequest authRequest) {
logger.debug("Authenticating request for URI: {}", authRequest.getOriginalUri());
// Extract session ID from cookie
String sessionId = sessionService.extractSessionIdFromCookie(authRequest.getCookie());
// Extract token from Authorization header (if provided)
String authHeader = extractAuthHeader(authRequest.getCookie());
String token = sessionService.extractTokenFromHeader(authHeader);
// Try session-based authentication first
if (sessionId != null) {
SessionService.SessionData sessionData = sessionService.getSession(sessionId);
if (sessionData != null) {
logger.debug("Session authentication successful for user: {}", sessionData.getUserId());
return createSuccessResponse(sessionData.getUserId(), sessionData.getRoles());
}
}
// Try token-based authentication
if (token != null && jwtTokenService.isTokenValid(token)) {
String userId = jwtTokenService.getUserIdFromToken(token);
Set<String> roles = jwtTokenService.getRolesFromToken(token);
if (userId != null) {
logger.debug("Token authentication successful for user: {}", userId);
return createSuccessResponse(userId, roles);
}
}
// Check for API key or other authentication methods
if (isApiKeyValid(authRequest)) {
String apiKeyUserId = validateApiKey(authRequest);
if (apiKeyUserId != null) {
Set<String> roles = userService.getUserRoles(apiKeyUserId);
logger.debug("API key authentication successful for user: {}", apiKeyUserId);
return createSuccessResponse(apiKeyUserId, roles);
}
}
logger.debug("Authentication failed for URI: {}", authRequest.getOriginalUri());
return createFailureResponse("Authentication required");
}
public boolean authorizeRequest(AuthResponse authResponse, String resource, String method) {
if (!authResponse.isAuthenticated()) {
return false;
}
// Implement authorization logic based on roles and permissions
Set<String> userRoles = authResponse.getRoles();
String userId = authResponse.getUserId();
// Check if user has access to the resource
return hasAccess(userId, userRoles, resource, method);
}
private AuthResponse createSuccessResponse(String userId, Set<String> roles) {
AuthResponse response = new AuthResponse();
response.setAuthenticated(true);
response.setUserId(userId);
response.setRoles(roles);
response.setMessage("Authentication successful");
return response;
}
private AuthResponse createFailureResponse(String message) {
AuthResponse response = new AuthResponse();
response.setAuthenticated(false);
response.setMessage(message);
return response;
}
private String extractAuthHeader(String cookieHeader) {
// In NGINX auth request, cookies might contain the Authorization header
// This is a simplified implementation
if (cookieHeader != null && cookieHeader.contains("Authorization=")) {
String[] parts = cookieHeader.split("Authorization=");
if (parts.length > 1) {
return parts[1].split(";")[0];
}
}
return null;
}
private boolean isApiKeyValid(AuthRequest authRequest) {
// Implement API key validation logic
return false; // Simplified
}
private String validateApiKey(AuthRequest authRequest) {
// Implement API key validation
return null; // Simplified
}
private boolean hasAccess(String userId, Set<String> roles, String resource, String method) {
// Implement role-based access control
if (roles.contains("ROLE_ADMIN")) {
return true;
}
// Check specific resource permissions
if (resource.startsWith("/api/admin") && !roles.contains("ROLE_ADMIN")) {
return false;
}
if (resource.startsWith("/api/user") && !roles.contains("ROLE_USER")) {
return false;
}
return true;
}
}

9. User Service

User Service:

package com.myapp.nginx.auth.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public boolean validateCredentials(String username, String password) {
// Implement user credential validation
// This could be against a database, LDAP, etc.
// For demo purposes - in production, use proper authentication
return "admin".equals(username) && "password".equals(password);
}
public Set<String> getUserRoles(String userId) {
// Implement role retrieval logic
// This could be from a database, etc.
// For demo purposes
if ("admin".equals(userId)) {
return Set.of("ROLE_ADMIN", "ROLE_USER");
} else if ("user".equals(userId)) {
return Set.of("ROLE_USER");
}
return Set.of();
}
public String getUserIdByUsername(String username) {
// Implement user ID lookup
return username; // Simplified
}
public boolean isUserActive(String userId) {
// Implement user status check
return true; // Simplified
}
}

10. Rate Limiting Service

Rate Limiting Service:

package com.myapp.nginx.auth.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RateLimitingService {
private static final Logger logger = LoggerFactory.getLogger(RateLimitingService.class);
private final RedisTemplate<String, Object> redisTemplate;
@Value("${app.auth.rate-limit.enabled:true}")
private boolean rateLimitEnabled;
@Value("${app.auth.rate-limit.requests-per-minute:60}")
private int requestsPerMinute;
@Value("${app.auth.rate-limit.burst-capacity:100}")
private int burstCapacity;
public RateLimitingService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean isAllowed(String clientId, String endpoint) {
if (!rateLimitEnabled) {
return true;
}
String key = getRateLimitKey(clientId, endpoint);
Long currentCount = redisTemplate.opsForValue().increment(key);
if (currentCount == 1) {
// First request, set expiration
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
boolean allowed = currentCount <= burstCapacity;
if (!allowed) {
logger.warn("Rate limit exceeded for client: {} on endpoint: {}", clientId, endpoint);
}
return allowed;
}
public int getRemainingRequests(String clientId, String endpoint) {
String key = getRateLimitKey(clientId, endpoint);
Long currentCount = redisTemplate.opsForValue().increment(key, 0); // Get without incrementing
return Math.max(0, burstCapacity - (currentCount != null ? currentCount.intValue() : 0));
}
private String getRateLimitKey(String clientId, String endpoint) {
return "rate_limit:" + clientId + ":" + endpoint + ":" + (System.currentTimeMillis() / 60000);
}
}

11. REST Controllers

Auth Controller:
```java
package com.myapp.nginx.auth.controller;

import com.myapp.nginx.auth.model.; import com.myapp.nginx.auth.service.;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Set;
import java.util.UUID;

@RestController
@RequestMapping("/auth")
public class AuthController {

private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
private final AuthenticationService authenticationService;
private final SessionService sessionService;
private final JwtTokenService jwtTokenService;
private final UserService userService;
private final RateLimitingService rateLimitingService;
@Value("${app.auth.session.cookie-name:AUTH_SESSION}")
private String sessionCookieName;
@Value("${app.auth.session.secure-cookie:true}")
private boolean secureCookie;
public AuthController(AuthenticationService authenticationService,
SessionService sessionService,
JwtTokenService jwtTokenService,
UserService userService,
RateLimitingService rateLimitingService) {
this.authenticationService = authenticationService;
this.sessionService = sessionService;
this.jwtTokenService = jwtTokenService;
this.userService = userService;
this.rateLimitingService = rateLimitingService;
}
@PostMapping("/verify")
public ResponseEntity<Void> verifyAuthentication(
@RequestHeader(value = "X-Original-URI", required = false) String originalUri,
@RequestHeader(value = "X-Original-Method", required = false) String originalMethod,
@RequestHeader(value = "X-Real-IP", required = false) String realIp,
@RequestHeader(value = "X-Forwarded-For", required = false) String forwardedFor,
@RequestHeader(value = "Cookie", required = false) String cookie,
HttpServletResponse response) {
// Create auth request
AuthRequest authRequest = new AuthRequest();
authRequest.setOriginalUri(originalUri);
authRequest.setOriginalMethod(originalMethod);
authRequest.setRealIp(realIp);
authRequest.setForwardedFor(forwardedFor);
authRequest.setCookie(cookie);
// Apply rate limiting
String clientId = realIp != null ? realIp : "unknown";
if (!rateLimitingService.isAllowed(clientId, "auth_verify")) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build();
}
// Authenticate request
AuthResponse authResponse = authenticationService.authenticateRequest(authRequest);
if (authResponse.isAuthenticated()) {
// Check authorization
boolean authorized = authenticationService.authorizeRequest(
authResponse, originalUri, originalMethod);
if (authorized) {
// Set user headers for NGINX
response.setHeader("X-User", authResponse.getUserId());
response.setHeader("X-Roles", String.join(",", authResponse.getRoles()));
response.setHeader("X-Auth-Time", LocalDateTime.now().toString());
logger.debug("Authentication successful for user: {}", authResponse.getUserId());
return ResponseEntity.ok().build();
} else {
logger.warn("Authorization failed for user: {} on resource: {}", 
authResponse.getUserId(), originalUri);
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
} else {
logger.debug("Authentication failed for resource: {}", originalUri);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest,
HttpServletResponse response) {
// Apply rate limiting
if (!rateLimitingService.isAllowed(loginRequest.getUsername(), "login")) {
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.body(createLoginResponse(false, "Too many login attempts"));
}
// Validate credentials
if (!userService.validateCredentials(loginRequest.getUsername(), loginRequest.getPassword())) {
logger.warn("Failed login attempt for user: {}", loginRequest.getUsername());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(createLoginResponse(false, "Invalid credentials"));
}
// Check if user is active
String userId = userService.getUserIdByUsername(loginRequest.getUsername());
if (!userService.isUserActive(userId)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(createLoginResponse(false, "User account is disabled"));
}
// Get user roles
Set<String> roles = userService.getUserRoles(userId);
// Create session
String sessionId = UUID.randomUUID().toString();
sessionService.createSession(sessionId, userId, roles);
// Generate JWT token
String token = jwtTokenService.generateToken(userId, roles, null);
LocalDateTime expiresAt = jwtTokenService.getExpirationDateFromToken(token);
// Set session cookie
String cookieValue = String.format("%s=%s; Path=/; HttpOnly; %s", 
sessionCookieName, sessionId,
secureCookie ? "Secure" : "");
response.setHeader(HttpHeaders.SET_COOKIE, cookieValue);
logger.info("Successful login for user: {}", userId);
LoginResponse loginResponse = createLoginResponse(true, "Login successful");
loginResponse.setToken(token);
loginResponse.setUserId(userId);
loginResponse.setRoles(roles);
loginResponse.setExpiresAt(expiresAt);
return ResponseEntity.ok(loginResponse);
}
@PostMapping("/logout")
public ResponseEntity<LogoutResponse> logout(
@RequestHeader(value = "Cookie", required = false) String cookie,
HttpServletResponse response) {
String sessionId = sessionService.extractSessionIdFromCookie(cookie);
if (sessionId != null) {
sessionService.deleteSession(sessionId);
logger.debug("User logged out, session: {}", sessionId);
}
// Clear session cookie
String cookieValue = String.format("%s=; Path=/; HttpOnly; %s; Max-Age=0", 
sessionCookieName,
secureCookie ? "Secure" : "");
response.setHeader(HttpHeaders.SET_COOKIE, cookieValue);
LogoutResponse logoutResponse = new LogoutResponse();
logoutResponse.setSuccess(true);
logoutResponse.setMessage("Logout successful");
return ResponseEntity.ok(logoutResponse);
}
@GetMapping("/health")
public ResponseEntity<HealthResponse> health() {
HealthResponse healthResponse = new HealthResponse();
healthResponse.setStatus("UP");
healthResponse.setTimestamp(LocalDateTime.now());
healthResponse.setVersion("1.0.0");
return ResponseEntity.ok(healthResponse);
}
private LoginResponse createLoginResponse(boolean success, String message) {
LoginResponse response = new LoginResponse();
response.setSuccess(success);
response.setMessage(message);
return response;
}
public static class LogoutResponse {
private boolean success;
private String message;
// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
public static class HealthResponse {
private String status;
private LocalDateTime timestamp;
private String version;
// Getters and Setters
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public LocalDateTime getTimestamp() {

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper