You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+REST后端会话管理需求及Spring Boot+Map方案咨询

Hey there! I totally get where you're coming from—OAuth2 + JWT can feel like overkill when your authorization and resource servers are part of the same app, especially since you're starting with OTP-based auth. Let's walk through a clean, lightweight session management implementation using Spring Boot with a simple map-based approach, tailored exactly for your use case.

Core Approach

Since your app's auth and resource layers live together, server-side session tracking is a perfect fit. We'll use a thread-safe map to store user sessions after OTP verification, pair it with HttpOnly cookies for secure session ID transmission, and add guards to validate sessions on every subsequent request.

Step-by-Step Implementation

1. Define a Session Entity

First, create a class to hold session details (user info, timestamps):

public class UserSession {
    private String userId;
    private String username;
    private LocalDateTime createdAt;
    private LocalDateTime expiresAt;

    // Constructor, getters, and setters
    public UserSession(String userId, String username, LocalDateTime createdAt, LocalDateTime expiresAt) {
        this.userId = userId;
        this.username = username;
        this.createdAt = createdAt;
        this.expiresAt = expiresAt;
    }

    // Add getters for all fields
}

2. Build a Thread-Safe Session Manager

Use ConcurrentHashMap to handle concurrent session access, plus methods to create, refresh, invalidate, and clean up sessions:

@Component
public class SessionManager {
    private final ConcurrentHashMap<String, UserSession> activeSessions = new ConcurrentHashMap<>();
    private static final int SESSION_EXPIRY_MINUTES = 30;

    // Create a new session after OTP verification
    public String createSession(String userId, String username) {
        String sessionId = UUID.randomUUID().toString();
        LocalDateTime now = LocalDateTime.now();
        UserSession session = new UserSession(
            userId,
            username,
            now,
            now.plusMinutes(SESSION_EXPIRY_MINUTES)
        );
        activeSessions.put(sessionId, session);
        return sessionId;
    }

    // Fetch session and refresh its expiry time on each valid request
    public UserSession getAndRefreshSession(String sessionId) {
        UserSession session = activeSessions.get(sessionId);
        if (session != null && !isSessionExpired(session)) {
            session.setExpiresAt(LocalDateTime.now().plusMinutes(SESSION_EXPIRY_MINUTES));
            return session;
        }
        // Remove expired/invalid sessions immediately
        activeSessions.remove(sessionId);
        return null;
    }

    // Invalidate session on logout
    public void invalidateSession(String sessionId) {
        activeSessions.remove(sessionId);
    }

    // Helper to check if a session is expired
    private boolean isSessionExpired(UserSession session) {
        return LocalDateTime.now().isAfter(session.getExpiresAt());
    }

    // Scheduled task to clean up expired sessions (runs every minute)
    @Scheduled(fixedRate = 60000)
    public void cleanExpiredSessions() {
        activeSessions.entrySet().removeIf(entry -> isSessionExpired(entry.getValue()));
    }
}

3. Integrate with OTP Authentication

After validating the OTP, create a session and send the session ID back via an HttpOnly cookie:

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private SessionManager sessionManager;
    @Autowired
    private OtpService otpService; // Your existing OTP validation service
    @Autowired
    private UserRepository userRepository; // Your user data repository

    @PostMapping("/verify-otp")
    public ResponseEntity<?> verifyOtp(
        @RequestBody OtpVerificationRequest request,
        HttpServletResponse response
    ) {
        // Validate the provided OTP
        if (!otpService.validateOtp(request.getPhoneNumber(), request.getOtp())) {
            return ResponseEntity.badRequest().body("Invalid OTP");
        }

        // Fetch user details from your database
        User user = userRepository.findByPhoneNumber(request.getPhoneNumber())
            .orElseThrow(() -> new RuntimeException("User not found"));

        // Create a new session
        String sessionId = sessionManager.createSession(user.getId(), user.getUsername());

        // Set HttpOnly cookie (prevents XSS attacks)
        Cookie sessionCookie = new Cookie("APP_SESSION_ID", sessionId);
        sessionCookie.setHttpOnly(true);
        sessionCookie.setSecure(true); // Enable in production (HTTPS only)
        sessionCookie.setPath("/");
        sessionCookie.setMaxAge(SESSION_EXPIRY_MINUTES * 60); // Match session expiry
        response.addCookie(sessionCookie);

        return ResponseEntity.ok("OTP verified. Session started.");
    }

    @PostMapping("/logout")
    public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
        // Find and invalidate the session ID from cookies
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("APP_SESSION_ID".equals(cookie.getName())) {
                    sessionManager.invalidateSession(cookie.getValue());
                    // Clear the cookie from the client
                    cookie.setValue("");
                    cookie.setPath("/");
                    cookie.setMaxAge(0);
                    response.addCookie(cookie);
                    break;
                }
            }
        }
        return ResponseEntity.ok("Logged out successfully.");
    }
}

4. Add a Session Validation Interceptor

Create an interceptor to check for valid sessions on all non-auth endpoints:

@Component
public class SessionValidationInterceptor implements HandlerInterceptor {
    @Autowired
    private SessionManager sessionManager;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Skip validation for auth endpoints
        String requestPath = request.getRequestURI();
        if (requestPath.startsWith("/auth/")) {
            return true;
        }

        // Extract session ID from cookies
        String sessionId = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("APP_SESSION_ID".equals(cookie.getName())) {
                    sessionId = cookie.getValue();
                    break;
                }
            }
        }

        // Validate session existence and expiry
        if (sessionId == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No active session found.");
            return false;
        }

        UserSession session = sessionManager.getAndRefreshSession(sessionId);
        if (session == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Session expired or invalid.");
            return false;
        }

        // Attach user info to the request context for downstream use
        request.setAttribute("LOGGED_IN_USER_ID", session.getUserId());
        request.setAttribute("LOGGED_IN_USERNAME", session.getUsername());
        return true;
    }
}

5. Register the Interceptor

Add the interceptor to your Spring Boot web configuration:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private SessionValidationInterceptor sessionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(sessionInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/auth/**");
    }
}
Key Considerations
  • Security: Using HttpOnly cookies prevents XSS attacks from stealing session IDs. Enable Secure mode in production to ensure cookies are only sent over HTTPS.
  • Persistence: For production environments where server restarts can't lose sessions, replace the ConcurrentHashMap with Redis. The SessionManager logic will stay almost identical—just swap map operations with RedisTemplate calls.
  • CSRF Protection: Since you're using cookies, enable Spring's CSRF protection (it's on by default in Spring Security) to prevent cross-site request forgery.
  • Angular Integration: Your Angular app doesn't need extra code to handle cookies—browsers automatically attach them to same-domain requests. If your frontend is on a different domain, configure CORS in Spring to allow credentials.

内容的提问来源于stack exchange,提问作者osama yaccoub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:19:47