33import java .util .HashMap ;
44import java .util .Map ;
55import java .util .Optional ;
6- import java .util .concurrent .ConcurrentHashMap ;
76
8- import org .springframework .http .HttpStatus ;
97import org .springframework .http .ResponseEntity ;
108import org .springframework .security .core .Authentication ;
11- import org .springframework .security .core .context .SecurityContextHolder ;
12- import org .springframework .security .oauth2 .client .authentication .OAuth2AuthenticationToken ;
139import org .springframework .security .oauth2 .core .user .OAuth2User ;
1410import org .springframework .security .web .authentication .logout .SecurityContextLogoutHandler ;
1511import org .springframework .web .bind .annotation .GetMapping ;
2016import com .otavio .aifoodapp .dto .UserDTO ;
2117import com .otavio .aifoodapp .model .User ;
2218import com .otavio .aifoodapp .repository .UserRepository ;
23- import com .otavio .aifoodapp .security .TokenService ;
24- import com .otavio .aifoodapp .service .FoodItemService ;
2519
26- import jakarta .servlet .http .Cookie ;
2720import jakarta .servlet .http .HttpServletRequest ;
2821import jakarta .servlet .http .HttpServletResponse ;
29- import jakarta .servlet .http .HttpSession ;
3022import lombok .extern .slf4j .Slf4j ;
3123
3224/**
33- * Controlador consolidado para autenticação
34- * Combina funcionalidades de status, login, logout e gerenciamento de tokens
25+ * Standard OAuth2 Authentication Controller
26+ * Uses Spring Security OAuth2 without custom token handling
3527 */
3628@ RestController
3729@ RequestMapping ("/api/auth" )
3830@ Slf4j
3931public class AuthController {
4032
41- private final FoodItemService foodItemService ;
42- private final TokenService tokenService ;
4333 private final UserRepository userRepository ;
4434
45- // Cache para controlar a frequência de verificações por sessão
46- private final Map <String , Long > lastStatusChecks = new ConcurrentHashMap <>();
47- private static final long STATUS_CHECK_THROTTLE_MS = 2000 ; // 2 segundos
48-
49- public AuthController (FoodItemService foodItemService , TokenService tokenService , UserRepository userRepository ) {
50- this .foodItemService = foodItemService ;
51- this .tokenService = tokenService ;
35+ public AuthController (UserRepository userRepository ) {
5236 this .userRepository = userRepository ;
5337 }
5438
55-
5639 /**
57- * Verificar informações do usuário atual
58- * Endpoint alternativo para obter dados do usuário autenticado
40+ * Get current authenticated user information
5941 */
6042 @ GetMapping ("/me" )
6143 public ResponseEntity <UserDTO > getCurrentUser (Authentication authentication ) {
@@ -84,7 +66,7 @@ public ResponseEntity<UserDTO> getCurrentUser(Authentication authentication) {
8466 }
8567
8668 /**
87- * Endpoint para logout
69+ * Logout endpoint
8870 */
8971 @ PostMapping ("/logout" )
9072 public ResponseEntity <Map <String , String >> logout (HttpServletRequest request ,
@@ -99,7 +81,7 @@ public ResponseEntity<Map<String, String>> logout(HttpServletRequest request,
9981 }
10082
10183 /**
102- * Endpoint para obter URL de login do Google
84+ * Get Google OAuth2 login URL
10385 */
10486 @ GetMapping ("/login/google" )
10587 public ResponseEntity <Map <String , String >> getGoogleLoginUrl () {
@@ -110,189 +92,35 @@ public ResponseEntity<Map<String, String>> getGoogleLoginUrl() {
11092 }
11193
11294 /**
113- * Verificar se o usuário está autenticado e retornar detalhes
114- * Usado pelo frontend para verificar autenticação persistente
95+ * Check authentication status
11596 */
11697 @ GetMapping ("/status" )
117- public ResponseEntity <?> authStatus (HttpServletRequest request ) {
118- log .info ("=== AUTH STATUS CHECK ===" );
119- log .info ("Request URI: {}, Method: {}" , request .getRequestURI (), request .getMethod ());
120- log .info ("Remote IP: {}, User-Agent: {}" , request .getRemoteAddr (), request .getHeader ("User-Agent" ));
121- log .info ("Host: {}, Origin: {}, Referer: {}" ,
122- request .getHeader ("Host" ),
123- request .getHeader ("Origin" ),
124- request .getHeader ("Referer" ));
98+ public ResponseEntity <Map <String , Object >> authStatus (Authentication authentication ) {
99+ log .debug ("Checking authentication status" );
100+
101+ if (authentication != null && authentication .isAuthenticated () &&
102+ !"anonymousUser" .equals (authentication .getPrincipal ())) {
103+
104+ log .debug ("User is authenticated: {}" , authentication .getName ());
105+
106+ if (authentication .getPrincipal () instanceof OAuth2User oauth2User ) {
107+ String email = oauth2User .getAttribute ("email" );
108+ String name = oauth2User .getAttribute ("name" );
109+ String picture = oauth2User .getAttribute ("picture" );
125110
126- try {
127- // Get session ID or IP if no session exists
128- HttpSession session = request .getSession (false );
129- String sessionId = session != null ? session .getId () : request .getRemoteAddr ();
130-
131- // Check request frequency for this endpoint
132- long now = System .currentTimeMillis ();
133- Long lastCheck = lastStatusChecks .get (sessionId );
134-
135- // If last check was too recent, return 429 Too Many Requests
136- if (lastCheck != null && now - lastCheck < STATUS_CHECK_THROTTLE_MS ) {
137- log .warn ("Too many requests to /api/auth/status from {}" , sessionId );
138- return ResponseEntity .status (HttpStatus .TOO_MANY_REQUESTS )
139- .body (Map .of ("error" , "Too many requests. Please wait." ));
140- }
141-
142- // Update last check timestamp
143- lastStatusChecks .put (sessionId , now );
144-
145- // Limit cache size (remove old entries with 1% probability)
146- if (lastStatusChecks .size () > 1000 && Math .random () < 0.01 ) {
147- cleanOldEntries ();
148- }
149-
150- Authentication auth = SecurityContextHolder .getContext ().getAuthentication ();
151-
152- // Debug session information
153- log .debug ("Auth status check with session ID: {}" , sessionId );
154- log .debug ("Request URI: {}, Method: {}" , request .getRequestURI (), request .getMethod ());
155- log .debug ("User-Agent: {}" , request .getHeader ("User-Agent" ));
156- log .debug ("Origin: {}" , request .getHeader ("Origin" ));
157- log .debug ("Referer: {}" , request .getHeader ("Referer" ));
158-
159- // Check cookies for diagnostics
160- Cookie [] cookies = request .getCookies ();
161- if (cookies == null ) {
162- log .debug ("No cookies present in status request" );
163- } else {
164- boolean foundJsessionId = false ;
165- for (Cookie cookie : cookies ) {
166- if ("JSESSIONID" .equals (cookie .getName ())) {
167- foundJsessionId = true ;
168- log .debug ("JSESSIONID cookie found: domain={}, path={}, maxAge={}, secure={}, httpOnly={}" ,
169- cookie .getDomain () != null ? cookie .getDomain () : "default" ,
170- cookie .getPath (),
171- cookie .getMaxAge (),
172- cookie .getSecure (),
173- cookie .isHttpOnly ());
174- }
175- }
176- if (!foundJsessionId ) {
177- log .warn ("JSESSIONID cookie not found in status request" );
178- }
179- }
180-
181- // Create session if it doesn't exist to ensure we have a valid session for all requests
182- if (session == null ) {
183- session = request .getSession (true );
184- log .debug ("Created new session for auth status check: {}" , session .getId ());
185- }
186-
187- if (auth != null && auth .isAuthenticated () && !"anonymousUser" .equals (auth .getPrincipal ())) {
188- log .info ("User is authenticated as: {}" , auth .getName ());
189-
190- try {
191- // Ensure security context is in session
192- session .setAttribute ("SPRING_SECURITY_CONTEXT" , SecurityContextHolder .getContext ());
193-
194- // Get current user info from service
195- User user = foodItemService .getUserForTesting ();
196- if (user != null ) {
197- log .debug ("Successfully retrieved user details for: {}" , user .getEmail ());
198- return ResponseEntity .ok (Map .of (
199- "authenticated" , true ,
200- "user" , Map .of (
201- "id" , user .getId (),
202- "name" , user .getFirstName () != null && user .getLastName () != null ?
203- user .getFirstName () + " " + user .getLastName () : user .getEmail (),
204- "email" , user .getEmail (),
205- "picture" , user .getProfilePicture () != null ? user .getProfilePicture () : ""
206- ),
207- "sessionId" , session .getId (),
208- "sessionCreatedAt" , new java .util .Date (session .getCreationTime ()).toString ()
209- ));
210- } else {
211- log .warn ("User object is null for authenticated user: {}" , auth .getName ());
212- return ResponseEntity .ok (Map .of (
213- "authenticated" , true ,
214- "sessionId" , session .getId (),
215- "error" , "User details unavailable" ,
216- "message" , "Session is authenticated but user details cannot be retrieved"
217- ));
218- }
219- } catch (NullPointerException e ) {
220- log .error ("NullPointerException in auth status check" , e );
221- // Return basic authentication info without user details
222- return ResponseEntity .ok (Map .of (
223- "authenticated" , true ,
224- "sessionId" , session .getId (),
225- "error" , "User details unavailable" ,
226- "message" , "Session is authenticated but user details cannot be retrieved"
227- ));
228- } catch (Exception e ) {
229- log .error ("Error getting user details: {}" , e .getMessage (), e );
230- return ResponseEntity .ok (Map .of (
231- "authenticated" , true ,
232- "sessionId" , session .getId (),
233- "error" , "Error fetching user details" ,
234- "message" , e .getMessage ()
235- ));
236- }
237- } else {
238- log .debug ("User is not authenticated. Auth: {}" , auth );
239- }
240-
241- return ResponseEntity .ok (Map .of (
242- "authenticated" , false ,
243- "sessionId" , sessionId
244- ));
245- } catch (Exception e ) {
246- log .error ("Unexpected error in auth status endpoint" , e );
247- return ResponseEntity .status (HttpStatus .INTERNAL_SERVER_ERROR )
248- .body (Map .of (
249- "error" , "Internal server error" ,
250- "message" , e .getMessage (),
251- "path" , request .getRequestURI ()
252- ));
253- }
254- }
255-
256- /**
257- * Manually trigger token refresh if needed
258- * This can be called by the frontend to ensure tokens are valid
259- * @param request the HTTP request
260- * @return Success status of the refresh operation
261- */
262- @ PostMapping ("/refresh" )
263- public ResponseEntity <?> refreshToken (HttpServletRequest request ) {
264- Authentication auth = SecurityContextHolder .getContext ().getAuthentication ();
265-
266- if (auth instanceof OAuth2AuthenticationToken oauth2Auth ) {
267- try {
268- boolean refreshed = tokenService .refreshAccessTokenIfNeeded (oauth2Auth );
269- return ResponseEntity .ok (Map .of (
270- "success" , refreshed ,
271- "message" , refreshed ? "Token refreshed successfully" : "Token refresh not needed"
272- ));
273- } catch (Exception e ) {
274- log .error ("Error refreshing token" , e );
275111 return ResponseEntity .ok (Map .of (
276- "success" , false ,
277- "message" , "Error refreshing token: " + e .getMessage ()
112+ "authenticated" , true ,
113+ "user" , Map .of (
114+ "email" , email != null ? email : "" ,
115+ "name" , name != null ? name : "" ,
116+ "picture" , picture != null ? picture : ""
117+ )
278118 ));
279119 }
120+
121+ return ResponseEntity .ok (Map .of ("authenticated" , true ));
280122 }
281-
282- return ResponseEntity .ok (Map .of (
283- "success" , false ,
284- "message" , "Not an OAuth2 authentication"
285- ));
286- }
287-
288- /**
289- * Limpa entradas antigas do cache de verificações de status
290- */
291- private void cleanOldEntries () {
292- long currentTime = System .currentTimeMillis ();
293- long threshold = currentTime - (STATUS_CHECK_THROTTLE_MS * 10 ); // 10x o tempo de throttle
294-
295- lastStatusChecks .entrySet ().removeIf (entry -> entry .getValue () < threshold );
296- log .debug ("Cache de verificações de status limpo. Tamanho atual: {}" , lastStatusChecks .size ());
123+
124+ return ResponseEntity .ok (Map .of ("authenticated" , false ));
297125 }
298126}
0 commit comments