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

基于Spring Security实现用户最后登录时间戳存储方案咨询

Solution to Record One-Time Last Login Timestamp

Alright, let's tackle this problem step by step. You want to record a user's last login timestamp only once when they log in, not on every page refresh or request. Since your app is stateless (using SessionCreationPolicy.STATELESS), we can't rely on traditional session tracking—instead, we need to hook into the authentication success event to update the timestamp exactly once per login.

Step 1: Add the Last Login Timestamp Field to Your User Entity

First, update your UserEntity to include the timestamp field, along with its getter/setter. Don't forget to update your database schema (e.g., add a last_login_timestamp column) to store this value.

public class UserEntity implements UserDetails {
    // Your existing fields
    private Integer pdmUid;
    private Role role;
    private boolean onlineSystem;
    
    // New field for login timestamp
    private LocalDateTime lastLoginTimestamp;

    // Getter and Setter
    public LocalDateTime getLastLoginTimestamp() {
        return lastLoginTimestamp;
    }

    public UserEntity setLastLoginTimestamp(LocalDateTime lastLoginTimestamp) {
        this.lastLoginTimestamp = lastLoginTimestamp;
        return this;
    }

    // Keep your existing methods (like authorities, username, etc.)
}

Step 2: Update UserService to Handle Timestamp Updates

Add a method to your UserService that updates the timestamp for a given user:

@Service
public class UserService {
    private final UserRepository userRepository;

    // Constructor injection
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // Your existing methods (createIfNotExist, etc.)

    public void updateLastLoginTimestamp(Integer pdmUid) {
        UserEntity user = userRepository.findByPdmUid(pdmUid)
                .orElseThrow(() -> new RuntimeException("User not found with pdmUid: " + pdmUid));
        user.setLastLoginTimestamp(LocalDateTime.now());
        userRepository.save(user);
    }
}

Step 3: Hook into Authentication Success to Trigger the Update

Since you're using a custom authentication setup, we'll create an AuthenticationSuccessHandler to run the timestamp update only when the user successfully logs in (not on every subsequent request).

@Component
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler {
    private final UserService userService;

    public CustomAuthSuccessHandler(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // Extract the authenticated user's details
        UserEntity user = (UserEntity) authentication.getPrincipal();
        // Update the login timestamp once
        userService.updateLastLoginTimestamp(user.getPdmUid());
        
        // Continue with your existing post-login logic (e.g., generate JWT, send response)
        // ...
    }
}

Then, register this handler in your SecurityConfiguration to tie it to your authentication flow:

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    private final ApifyAuthConfigurer authConfigurer;
    private final PrincipalProvider principalProvider;
    private final CustomAuthSuccessHandler customAuthSuccessHandler; // Inject the handler

    // Update constructor to include the handler
    public SecurityConfiguration(ApifyAuthConfigurer authConfigurer, PrincipalProvider principalProvider, CustomAuthSuccessHandler customAuthSuccessHandler) {
        this.authConfigurer = authConfigurer;
        this.principalProvider = principalProvider;
        this.customAuthSuccessHandler = customAuthSuccessHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint(new ApifyAuthenticationEntryPoint())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // Attach the success handler to your authentication method (adjust if using OAuth2/JWT login)
                .formLogin()
                .successHandler(customAuthSuccessHandler);
        
        authConfigurer.configure(http, principalProvider);
    }
}

Step 4: Fix the PrincipalProvider to Avoid Unnecessary Updates

Your current PrincipalProvider runs on every request (since it parses JWT claims each time). We'll modify it to fetch the latest user data from the database instead of creating a new UserEntity—this ensures you can return the stored login timestamp in the /current endpoint.

public class PrincipalProvider implements UserPrincipalProvider<UserDetails> {
    private final UserService userService;

    public PrincipalProvider(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Optional<UserDetails> getPrincipal(Claims claims) {
        log.debug("#getPrincipal - {}", claims);
        Integer pdmUid = (Integer) claims.get("pdmUid");
        
        // Create user if they don't exist (existing logic)
        userService.createIfNotExist(claims);
        
        // Fetch the full user entity from DB to get the latest login timestamp
        UserEntity user = userService.getUserByPdmUid(pdmUid); // Add this method to UserService
        return Optional.of(user);
    }
}

Step 5: Update the /current Endpoint to Return the Timestamp

Modify your UserDto to include the timestamp, then update the controller to return it:

// UserDto class
public class UserDto {
    private Integer pdmUid;
    private String role;
    private boolean onlineSystem;
    private LocalDateTime lastLoginTimestamp; // New field

    // Constructor including the timestamp
    public UserDto(Integer pdmUid, String role, boolean onlineSystem, LocalDateTime lastLoginTimestamp) {
        this.pdmUid = pdmUid;
        this.role = role;
        this.onlineSystem = onlineSystem;
        this.lastLoginTimestamp = lastLoginTimestamp;
    }

    // Getters
}

// Controller update
@GetMapping("/current")
public ResponseEntity<UserDto> getCurrent() {
    Optional<UserDetails> currentFromSecurity = userSecurityService.getCurrentFromSecurity();
    if (!currentFromSecurity.isPresent()) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    } else {
        UserEntity userEntity = (UserEntity) currentFromSecurity.get();
        return ResponseEntity.ok(new UserDto(
                userEntity.getPdmUid(),
                userEntity.getRole().name(),
                userEntity.isOnlineSystem(),
                userEntity.getLastLoginTimestamp()
        ));
    }
}

Why This Works

  • The timestamp is only updated once per login (when the authentication success handler runs), not on every request.
  • Since your app is stateless, we rely on the authentication event to trigger the update—this aligns with your requirement to avoid updating on page refreshes or navigation.
  • Fetching the user from the database in PrincipalProvider ensures you always have the latest login timestamp to return to the frontend.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 21:47:46