Skip to content

Commit aad0e94

Browse files
feat(security): add session cookie configuration and global exception handling
1 parent 76214ae commit aad0e94

File tree

6 files changed

+178
-4
lines changed

6 files changed

+178
-4
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.otavio.aifoodapp.config;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.session.web.http.CookieSerializer;
8+
import org.springframework.session.web.http.DefaultCookieSerializer;
9+
10+
import lombok.extern.slf4j.Slf4j;
11+
12+
/**
13+
* Configuration for session cookies
14+
* This ensures cookies are properly set in production environment
15+
*/
16+
@Configuration
17+
@Slf4j
18+
public class SessionCookieConfig {
19+
20+
@Value("${COOKIE_SECURE:false}")
21+
private boolean cookieSecure;
22+
23+
@Value("${COOKIE_SAME_SITE:lax}")
24+
private String cookieSameSite;
25+
26+
@Value("${COOKIE_DOMAIN:}")
27+
private String cookieDomain;
28+
29+
/**
30+
* Configure cookie serializer for Spring Session
31+
*/
32+
@Bean
33+
public CookieSerializer cookieSerializer() {
34+
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
35+
36+
log.info("Configuring session cookies with secure={}, sameSite={}, domain={}",
37+
cookieSecure, cookieSameSite, cookieDomain.isEmpty() ? "default" : cookieDomain);
38+
39+
// Set secure flag based on environment
40+
serializer.setUseSecureCookie(cookieSecure);
41+
42+
// Set cookie name
43+
serializer.setCookieName("JSESSIONID");
44+
45+
// Set domain if provided
46+
if (!cookieDomain.isEmpty()) {
47+
serializer.setDomainName(cookieDomain);
48+
log.info("Setting cookie domain to: {}", cookieDomain);
49+
}
50+
51+
// Set cookie path to root to be accessible from all paths
52+
serializer.setCookiePath("/");
53+
54+
// Set SameSite attribute
55+
// Note: DefaultCookieSerializer doesn't support SameSite directly in older Spring versions
56+
serializer.setSameSite(cookieSameSite);
57+
58+
// Configure session cookie for maximum browser compatibility
59+
serializer.setUseHttpOnlyCookie(true);
60+
61+
return serializer;
62+
}
63+
64+
/**
65+
* Configure SameSite attribute for all cookies
66+
*/
67+
@Bean
68+
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
69+
return CookieSameSiteSupplier.ofLax();
70+
}
71+
}

src/main/java/com/otavio/aifoodapp/controller/AuthController.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public AuthController(FoodItemService foodItemService, TokenService tokenService
5050

5151
@GetMapping("/status")
5252
public ResponseEntity<?> authStatus(HttpServletRequest request) {
53+
log.info("=== AUTH STATUS CHECK ===");
54+
log.info("Request URI: {}, Method: {}", request.getRequestURI(), request.getMethod());
55+
log.info("Remote IP: {}, User-Agent: {}", request.getRemoteAddr(), request.getHeader("User-Agent"));
56+
log.info("Host: {}, Origin: {}, Referer: {}",
57+
request.getHeader("Host"),
58+
request.getHeader("Origin"),
59+
request.getHeader("Referer"));
60+
5361
// Obter ID da sessão ou IP se não houver sessão
5462
HttpSession session = request.getSession(false);
5563
String sessionId = session != null ? session.getId() : request.getRemoteAddr();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.otavio.aifoodapp.controller;
2+
3+
import java.util.Map;
4+
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.ControllerAdvice;
8+
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import org.springframework.web.context.request.WebRequest;
10+
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
11+
12+
import lombok.extern.slf4j.Slf4j;
13+
14+
/**
15+
* Global exception handler for all API endpoints
16+
*/
17+
@ControllerAdvice
18+
@Slf4j
19+
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
20+
21+
/**
22+
* Handle all uncaught exceptions
23+
*/
24+
@ExceptionHandler(Exception.class)
25+
public ResponseEntity<?> handleAllExceptions(Exception ex, WebRequest request) {
26+
log.error("Unhandled exception: {}", ex.getMessage(), ex);
27+
28+
return ResponseEntity
29+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
30+
.body(Map.of(
31+
"error", "internal_server_error",
32+
"message", "An unexpected error occurred",
33+
"path", request.getDescription(false).replace("uri=", ""),
34+
"status", 500
35+
));
36+
}
37+
38+
/**
39+
* Handle security-related exceptions
40+
*/
41+
@ExceptionHandler({
42+
org.springframework.security.access.AccessDeniedException.class,
43+
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.class,
44+
org.springframework.security.authentication.BadCredentialsException.class,
45+
org.springframework.security.core.AuthenticationException.class
46+
})
47+
public ResponseEntity<?> handleSecurityExceptions(Exception ex, WebRequest request) {
48+
log.warn("Security exception: {}", ex.getMessage(), ex);
49+
50+
return ResponseEntity
51+
.status(HttpStatus.UNAUTHORIZED)
52+
.body(Map.of(
53+
"error", "unauthorized",
54+
"message", "Authentication required",
55+
"path", request.getDescription(false).replace("uri=", ""),
56+
"status", 401
57+
));
58+
}
59+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.otavio.aifoodapp.controller;
2+
3+
import java.util.Map;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
/**
10+
* Simple health check controller
11+
* Use this to check if the application is running correctly
12+
*/
13+
@RestController
14+
public class HealthController {
15+
16+
/**
17+
* Basic health check endpoint
18+
* This endpoint is publicly accessible and doesn't require authentication
19+
*/
20+
@GetMapping("/health")
21+
public ResponseEntity<?> healthCheck() {
22+
return ResponseEntity.ok(Map.of(
23+
"status", "UP",
24+
"service", "AiFoodApp",
25+
"timestamp", System.currentTimeMillis()
26+
));
27+
}
28+
}

src/main/java/com/otavio/aifoodapp/controller/UserInfoController.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.otavio.aifoodapp.controller;
22

3-
import java.util.Map;
43
import java.util.HashMap;
4+
import java.util.Map;
55

66
import org.springframework.http.ResponseEntity;
77
import org.springframework.security.core.Authentication;
@@ -37,6 +37,8 @@ public UserInfoController(UserService userService) {
3737
public ResponseEntity<?> getUserInfo() {
3838
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
3939

40+
log.info("GET /api/auth - Authentication: {}", auth != null ? auth.getName() : "null");
41+
4042
if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) {
4143
log.debug("Fornecendo informações para usuário autenticado: {}", auth.getName());
4244

@@ -72,9 +74,12 @@ public ResponseEntity<?> getUserInfo() {
7274
}
7375
}
7476

75-
log.debug("Tentativa de acesso a /api/auth sem autenticação");
76-
return ResponseEntity.status(401).body(Map.of(
77-
"error", "unauthorized",
77+
log.info("Tentativa de acesso a /api/auth sem autenticação (auth: {})",
78+
auth != null ? auth.getClass().getSimpleName() : "null");
79+
80+
// Return 200 with authenticated: false instead of 401 to avoid browser issues
81+
return ResponseEntity.ok(Map.of(
82+
"error", "not_authenticated",
7883
"authenticated", false,
7984
"message", "Usuário não autenticado"
8085
));

src/main/java/com/otavio/aifoodapp/security/SecurityConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, @Qualifier("co
7777
// Allow error endpoint
7878
.requestMatchers("/error").permitAll()
7979

80+
// Allow health check endpoint
81+
.requestMatchers("/health").permitAll()
82+
8083
// Allow test authentication endpoints for debugging
8184
.requestMatchers("/api/foods/test-auth").permitAll()
8285

0 commit comments

Comments
 (0)