Johnyb
Johnyb

Reputation: 1316

Hoes does spring-security filter fetches session information from redis?

I am trying to implement login system using spring-boot, spring-security and spring-session, and use redis as storage for the session.

My config:

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {


    private final UserDetailsService detailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider authenticationProvider(PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder);
        provider.setUserDetailsService(this.detailsService);
        return provider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationProvider authenticationProvider) {
        return new ProviderManager(authenticationProvider);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf().disable()
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/api/v1/auth/register/**", "/api/v1/auth/login").permitAll();
                    auth.anyRequest().authenticated();
                })
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(IF_REQUIRED) //
                        .sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::newSession) //
                        .maximumSessions(100000) //
                        //.sessionRegistry(sessionRegistry())
                )
                //.exceptionHandling((ex) -> ex.authenticationEntryPoint(this.authEntryPoint))
                .logout(out -> out
                        .logoutUrl("/api/v1/auth/logout")
                        .invalidateHttpSession(true) // Invalidate all sessions after logout
                        .deleteCookies("JSESSIONID")
                        .logoutSuccessHandler((request, response, authentication) ->
                                SecurityContextHolder.clearContext()
                        )
                )
                .build();
    }

    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }
}

My login controller:

@PostMapping("/login")
public void login(@RequestBody LoginForm form, HttpServletRequest request, HttpServletResponse response) {
    String ip = HttpRequestUtil.getIp(request);
    String device = HttpRequestUtil.getDevice(request);

    LoginFormWrapper loginFormWrapper = new LoginFormWrapper(form.email(), form.password(), ip, device);

    authenticationService.login(loginFormWrapper, request, response);
}

And authentication service:

@Override
public void login(LoginFormWrapper form, HttpServletRequest request, HttpServletResponse response) {
    Authentication authentication = authenticationManager.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(
            form.email().trim(), form.password()));

    // Create a new context
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(authentication);

    // Update SecurityContextHolder and Strategy
    this.securityContextHolderStrategy.setContext(context);
    this.securityContextRepository.saveContext(context, request, response);
}

If i understand it correctly

this.securityContextHolderStrategy.setContext(context); should save Authentication in app's memory, e.g in ThreadLocal context

and

    `this.securityContextRepository.saveContext(context, request, response);`

should save session info into redis.

Now when i log in I see the data getting saved into redis: enter image description here

However when check what is returned from my login request i see:

enter image description here

completely different ID for the session.

My first question is: How come these id's does not match? How does the spring know which key to look for?

Another question is: what filter does fetch the data from redis? I have tried to debug all filters in my filter chain:

[org.springframework.security.web.session.DisableEncodeUrlFilter@2fedae96, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4945cd1f, org.springframework.security.web.context.SecurityContextHolderFilter@72499396, org.springframework.security.web.header.HeaderWriterFilter@7048d039, org.springframework.web.filter.CorsFilter@2dbfcbe4, org.springframework.security.web.authentication.logout.LogoutFilter@5d5a77de, org.springframework.security.web.session.ConcurrentSessionFilter@1f8e1096, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@651bec9a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@76d4e1af, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6f13ed1, org.springframework.security.web.session.SessionManagementFilter@642693c2, org.springframework.security.web.access.ExceptionTranslationFilter@2199e1a4, org.springframework.security.web.access.intercept.AuthorizationFilter@48c584c]

yet it just seems that it is somehow reading the session information from HttpServletRequest - however if i delete keys from redis authentication fails for endpoints that require it.

Am i missing something? Is session info from redis retrieved before my fitler's begin and stored in HttpServlerRequest ? Or how does it read the redis data?

Thanks for help.

Upvotes: 1

Views: 632

Answers (1)

R4N
R4N

Reputation: 2595

My first question is: How come these id's does not match? How does the spring know which key to look for?

The value in the session cookie is base64 encoded:

echo '3c048eae-9f73-4df5-a009-bdf802ae37ca' | openssl base64
M2MwNDhlYWUtOWY3My00ZGY1LWEwMDktYmRmODAyYWUzN2NhCg==
echo 'M2MwNDhlYWUtOWY3My00ZGY1LWEwMDktYmRmODAyYWUzN2NhCg==' | openssl base64 -d
3c048eae-9f73-4df5-a009-bdf802ae37ca

So when base64 decoded, the cookie's session id matches the session id stored in redis.

Another question is: what filter does fetch the data from redis? I have tried to debug all filters in my filter chain:

If you haven't already reviewed it, I would recommend this documentation: https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html

Specifically the section on "Understanding Session Management's Components": https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html#understanding-session-management-components

You didn't mention which version of Spring Security you're using, but I surmise you're using Spring Security 6. Within that section, there's this sentence related to SessionAuthentication:

In Spring Security 6, the default is that authentication mechanisms themselves must invoke the SessionAuthenticationStrategy. This means that there is no need to detect when Authentication is done and thus the HttpSession does not need to be read for every request.

Upvotes: 1

Related Questions