Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JdbcOAuth2AuthorizedClientService should support update when saving #8425

Closed
stavshamir opened this issue Apr 22, 2020 · 3 comments
Closed
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@stavshamir
Copy link
Contributor

Description
Configuring JdbcOAuth2AuthorizedClientService as follows:

@Bean
public OAuth2AuthorizedClientService authorizedClientService(
		JdbcOperations jdbcOperations,
		ClientRegistrationRepository clientRegistrationRepository) {
	return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
}

Allows only one authorization of the same user - trying to log in from one browser, and then from another, throws a DuplicateKeyException, making it unusable for SSO.

More importantly, it throws the same exception when a refresh token is used for re-authorization when the access token is expired, meaning this class can't be used when re-authorization flow is required.

This behavior is inconsistent with the in memory implementation which is backed by a Map and put to save the authorized client.

To Reproduce

  1. Configure JdbcOAuth2AuthorizedClientService.
  2. Log in to an OAuth2 provider.
  3. Log in with the same user from another client - OR - wait for the access token to expire, and try to access a protected endpoint to trigger re-authorization.

Expected behavior
Re-authorization and login from a different client should not throw an exception. Instead, it should behave the same as InMemoryOAuth2AuthorizedClientService behaves when saveAuthorizedClient is called.

The solution to this bug must provide an atomic operation at the database level to be able to support concurrent access from multiple application instances. The only idea I had to achieve that is to use a ON DUPLICATE KEY UPDATE statement, but this will make this class non-generic.

If you have another idea, I would be glad to implement it and create a pull request.

@stavshamir stavshamir added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Apr 22, 2020
@jgrandja
Copy link
Contributor

@stavshamir Thank you for the detailed report.

Yes, I agree the solution to this bug will need to ensure an atomic update.
The ON DUPLICATE KEY UPDATE would be nice if it was standard but since it's not we cannot go with this option.

How about, saveAuthorizedClient() does an update if exists else insert. The transaction demarcation could be declared in a wrapper class of OAuth2AuthorizedClientService that simply delegates to JdbcOAuth2AuthorizedClientService?

Would you be interested in submitting a PR for this fix?

@jgrandja jgrandja added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 22, 2020
@jgrandja jgrandja added this to the 5.4.0.M1 milestone Apr 22, 2020
@jgrandja jgrandja changed the title Re-authorization and authorization requests for the same user from multiple clients while using JdbcOAuth2AuthorizedClientService throws DuplicateKeyException JdbcOAuth2AuthorizedClientService should support update when saving Apr 22, 2020
@stavshamir
Copy link
Contributor Author

Even if we wrap the use a transaction, the following scenario may occur:

  • App1 starts authorization for user X -> User X is not persisted
  • App2 start authorization for user X -> User X is not persisted -> User X is inserted
  • App1 continues to insert user X -> duplicate key is thrown

Then what? Retry?

After some more thought, I came up with this:

@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
    Assert.notNull(authorizedClient, "authorizedClient cannot be null");
    Assert.notNull(principal, "principal cannot be null");

    if (existsAuthorizedClient(authorizedClient, principal)) {
        updateAuthorizedClient(authorizedClient, principal);
    } else {
        try {
            insertAuthorizedClient(authorizedClient, principal);
        } catch (DuplicateKeyException e) {
            updateAuthorizedClient(authorizedClient, principal);
        }
    }
}

I believe this would make the result consistent with the in memory implementation.
Do you find this solution acceptable? I am interested to submit a PR.

@jgrandja
Copy link
Contributor

@stavshamir Good point. The scenario you describe is still an issue. The one thing I want to point out is that this issue is NOT a Spring Security issue but rather a Solution Architecture / Environment issue. Spring Security is not responsible for handling or co-ordinating distributed transactions. Instead this needs to be solved within the Application Architecture and Environment configuration. Makes sense?

Your proposed solution will definitely improve on the scenario you describe. Yes, please go with this and much appreciated for delivering this fix.

@jgrandja jgrandja added the for: backport-to-5.3.x Designates an issue for backport to 5.3.x label Apr 29, 2020
@spring-projects-issues spring-projects-issues added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x Designates an issue for backport to 5.3.x labels Apr 29, 2020
jgrandja pushed a commit that referenced this issue Apr 29, 2020
Before this commit, JdbcOAuth2AuthorizedClientService threw DuplicateKeyException when re-authorizing or when authorizing the same user from a different client.

This commit makes JdbcOAuth2AuthorizedClientService's saveAuthorizedClient method consistent with that of InMemoryOAuth2AuthorizedClientService.

Fixes gh-8425
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants