0

The use case is to store the user name/id in a database, when logging out. When trying to retrieve the username from the context, I will receive always null.

In Debug mode I evaluated this line in CustomLogoutHandler via my IDE (not in the code of CustomLogoutHandler) and it's null. SecurityContextHolder.getContext().getAuthentication().getName();

@Service
public class CustomLogoutHandler implements LogoutHandler {

    private final AuditorService auditorService;

    public CustomLogoutHandler(AuditorService auditorService) {
        this.auditorService = auditorService;
    }

    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
        auditorService.addLogoutActivity(ActivityEnum.LOGOUT, null, authentication.getName()); 
        // authentication object is null
    }
}

The SecurityConfiguration class


@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final JHipsterProperties jHipsterProperties;
    private final TokenProvider tokenProvider;
    private final CorsFilter corsFilter;
    private final SecurityProblemSupport problemSupport;
    private final DomainUserDetailsService domainUserDetailsService;
    private final ApplicationProperties applicationProperties;

    private final CustomLogoutHandler customLogoutHandler;

// constructor omitted

  @Override
    public void configure(WebSecurity web) {
        web
            .ignoring()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .antMatchers("/app/**/*.{js,html}")
            .antMatchers("/i18n/**")
            .antMatchers("/content/**")
            .antMatchers("/swagger-ui/**")
            .antMatchers("/test/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .logout(logout -> logout
                .logoutUrl("/api/logout")
                .logoutSuccessUrl("/login")
                .addLogoutHandler(customLogoutHandler)
                .invalidateHttpSession(true)
                .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
            )
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
            .and()
            .headers()
            .contentSecurityPolicy(jHipsterProperties.getSecurity().getContentSecurityPolicy())
            .and()
            .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
            .and()
            .permissionsPolicy().policy("camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()")
            .and()
            .frameOptions()
            .deny()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/authenticate").permitAll()
            .antMatchers("/api/logout").permitAll()
            .antMatchers("/api/register").permitAll()
            .antMatchers("/api/activate").permitAll()
            .antMatchers("/api/account/reset-password/init").permitAll()
            .antMatchers("/api/account/reset-password/finish").permitAll()
            .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/api/**").authenticated()
            .antMatchers("/websocket/**").authenticated()
            .antMatchers("/management/health").permitAll()
            .antMatchers("/management/health/**").permitAll()
            .antMatchers("/management/info").permitAll()
            .antMatchers("/management/prometheus").permitAll()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/audit/**").hasAuthority(AuthoritiesConstants.AUDITOR)
            .and()
            .httpBasic()
            .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    }

    // stuff omitted
}

I don't know where the context and the user name/id gets consumed in the sense of destroyed, before I can catch him/her, who issued the logout event and store it to the DB.

  • Is it because something else behind the scenes destroys the Authentication contents? I once had the issue, when two ApplicationListener<SessionDisconnectEvent> classes were listening to a disconnect. The first consumed the event and the SecurityContextHolder returned the user's name and did it's work and the second class with its onApplicationEvent(SessionDisconnectEvent event) couldn't work because SecurityContextHolder was null or empty (don't remember exactly). It seems, that SecurityContextHolder can only be used once for the identical event. Forgive me, but I don't understand Spring behind the curtains completely.

  • Shall I setup a "/custom_logout" as another endpoint and then internally navigate to spring's "/logout" endpoint, when the use case, described initially, is completed?

Semo
  • 783
  • 2
  • 17
  • 38

0 Answers0