Spring Boot oauth2 social login: Cannot login after session timeout

Viewed 153

I use social login in a spring boot container. This works fine, but when I keep the browser open over night, the next morning I cannot login any more. I need to restart the container and refresh the website, after this it works again.

I hope to get this work by storing the authentication token also in the database by adding JdbcOAuth2AuthorizedClientService. Now, and this is the actual problem, the token is NOT PERSITED although I get these lines in the log:

o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT client_registration_id, principal_name, access_token_type, access_token_value, access_token_issued_at, access_token_expires_at, access_token_scopes, refresh_token_value, refresh_token_issued_at FROM oauth2_authorized_client WHERE client_registration_id = ? AND principal_name = ?]
o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 1, parameter value [google], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 2, parameter value [XXXXXX], value class [java.lang.String], SQL type 12
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL update
o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [INSERT INTO oauth2_authorized_client (client_registration_id, principal_name, access_token_type, access_token_value, access_token_issued_at, access_token_expires_at, access_token_scopes, refresh_token_value, refresh_token_issued_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)]
o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 1, parameter value [google], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 2, parameter value [XXXXXX], value class [java.lang.String], SQL type 12
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 3, parameter value [Bearer], value class [java.lang.String], SQL type 12
o.s.jdbc.support.lob.DefaultLobHandler   : Set bytes for BLOB with length 165
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 5, parameter value [2022-04-25 09:56:57.137607903], value class [java.sql.Timestamp], SQL type 93
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 6, parameter value [2022-04-25 10:56:56.137607903], value class [java.sql.Timestamp], SQL type 93
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 7, parameter value [https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email,openid], value class [java.lang.String], SQL type 12
o.s.jdbc.support.lob.DefaultLobHandler   : Set BLOB to null
o.s.jdbc.core.StatementCreatorUtils      : Setting SQL statement parameter value: column index 9, parameter value [null], value class [null], SQL type 93
o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

The table keeps empty. I tried with H2 and also PostgreSQL but no difference. The application itself is able to write records to tables using Spring data. No problem at all.

Has anyone an idea? I appriciate for every hint :-)

Thx, SteveP

This is my security configuration:

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired final MyAppMemberService memberService;
    @Autowired private MyAppProperties properties;
    @Resource private ClientRegistrationRepository repo;
    @Resource private DataSource dataSource;
    
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        final RequestMatcher[] unprotectedGuiApi = new RequestMatcher[] {
                new AntPathRequestMatcher("/api/v*/gui/currentUser"),
                new AntPathRequestMatcher("/api/v*/gui/oauth2-clients"),
        };
        final RequestMatcher[] administrationApi = new RequestMatcher[] {
                new AntPathRequestMatcher("/api/v*/administration/**")
        };
        final RequestMatcher[] guiApi = new RequestMatcher[] {
                new AntPathRequestMatcher("/api/v*/**"),
                new AntPathRequestMatcher("/logout"),
                new AntPathRequestMatcher("/**/oauth2/**"),
        };
        http
                .requestMatchers()
                    .requestMatchers(guiApi)
                    .and()
                .csrf()
                    .disable()
                    .cors()
                    .configurationSource(httpRequest -> properties.getCors())
                    .and()
                .headers()
                    .contentTypeOptions().disable()
                    .frameOptions().disable()
                    .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .antMatchers("**/websocket/**").permitAll()
                    .requestMatchers(unprotectedGuiApi).permitAll()
                    .requestMatchers(administrationApi).hasRole(Role.ADMIN.name())
                    .anyRequest().authenticated()
                    .and()
                .logout()
                    .permitAll()
                    .logoutSuccessUrl(properties.getGatewayUrl()) // SPA endpoint
                    .and()
                .oauth2Login()
                    .loginPage(properties.getGatewayUrl() + "/login")
                    .authorizedClientService(oAuth2AuthorizedClientService())
                    .userInfoEndpoint()
                        .userService(oauth2UserService())
                        .and()
                    .successHandler(authenticationSuccessHandler());
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        // just registers new users on successful auth
        return new MyAppAuthenticationSuccessHandler(properties, memberService);
    }

    @Bean
    public OAuth2UserService oauth2UserService() {
        // load member and roles from database
        return new MyAppOAuth2UserService(memberService);

    }

    @Bean
    public OAuth2AuthorizedClientService oAuth2AuthorizedClientService() {
        return new JdbcOAuth2AuthorizedClientService(new JdbcTemplate(dataSource), repo);
    }
}
1 Answers

This post brought me the idea that it might not be committed into DB: When sharing a Jdbc connection pool between spring-data-jpa and spring-security-oauth's JdbcTokenStore, how to handle transactions?

Using this logging technique I could see the INSERT-statement was sent to DB https://vladmihalcea.com/the-best-way-to-log-jdbc-statements/ but no final log statement

o.s.jdbc.core.JdbcTemplate               : SQL update affected 1 rows

I adopted my WebSecurityConfiguration like this:

    @Bean
    public OAuth2AuthorizedClientService oAuth2AuthorizedClientService() {
        return new TransactionalJdbcOAuth2AuthorizedClientService(jdbcTemplate, repo);
    }

and added:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public class TransactionalJdbcOAuth2AuthorizedClientService extends JdbcOAuth2AuthorizedClientService {

    public TransactionalJdbcOAuth2AuthorizedClientService(final JdbcOperations jdbcOperations, final ClientRegistrationRepository clientRegistrationRepository) {
        super(jdbcOperations, clientRegistrationRepository);
    }
    
    @Override
    public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(final String clientRegistrationId, final String principalName) {
        return super.loadAuthorizedClient(clientRegistrationId, principalName);
    }
    
    @Override
    public void saveAuthorizedClient(final OAuth2AuthorizedClient authorizedClient, final Authentication principal) {
        super.saveAuthorizedClient(authorizedClient, principal);
    }
    
    @Override
    public void removeAuthorizedClient( final String clientRegistrationId, final String principalName) {
        super.removeAuthorizedClient(clientRegistrationId, principalName);
    }
}

Now it is persisted! I asked myself: Am I the first person using Spring's JdbcOAuth2AuthorizedClientService? No hint in the JavaDoc or in any Spring documentation. Anyway, hopefully this hint will help someone else.

Related