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

Support One-Time Tokens in a Clustered Environment #15735

Closed
franticticktick opened this issue Sep 4, 2024 · 10 comments · Fixed by #15842
Closed

Support One-Time Tokens in a Clustered Environment #15735

franticticktick opened this issue Sep 4, 2024 · 10 comments · Fixed by #15842
Assignees
Labels
in: web An issue in web modules (web, webmvc) type: enhancement A general enhancement
Milestone

Comments

@franticticktick
Copy link
Contributor

franticticktick commented Sep 4, 2024

It would be nice to implement session based OneTimeTokenService. Now this is difficult to do, because OneTimeTokenService accepts GenerateOneTimeTokenRequest. It is better to change the design of the generate method - replace GenerateOneTimeTokenRequest with HttpServletRequest.
It is not yet clear how you can call consume for an http session.

@franticticktick franticticktick added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Sep 4, 2024
@franticticktick
Copy link
Contributor Author

Maybe the best solution would be to split this component into two? For example OneTimeTokenGenerator and OneTimeTokenRepository.
Then

OneTimeToken ott = this.oneTimeTokenGenerator.generate(generateRequest);
this.oneTimeTokenGenerator.save(ott);

With this implementation it is much easier to ensure OneTimeTokenRepository works in a cluster environment.

@franticticktick franticticktick changed the title Consider adding http session based OneTimeTokenService Consider adding OneTimeTokenService for a clustered environment Sep 5, 2024
@marcusdacoregio marcusdacoregio self-assigned this Sep 13, 2024
@marcusdacoregio marcusdacoregio removed the status: waiting-for-triage An issue we've not yet triaged label Sep 13, 2024
@marcusdacoregio
Copy link
Contributor

Hi @CrazyParanoid, thanks for the report.

Can you elaborate more on your use case? Would you like to authenticate the session that started the request instead of the session that submits the token?

An alternative that I had in mind is to provide a GenerateOneTimeTokenRequestResolver that can be used in the GenerateOneTimeTokenFilter, something like:

public interface GenerateOneTimeTokenRequestResolver {

    GenerateOneTimeTokenRequest resolve(HttpServletRequest request);

}

This way you can return a subclass of GenerateOneTimeTokenRequest with the appropriate information.

@franticticktick
Copy link
Contributor Author

Hi @marcusdacoregio

An alternative that I had in mind is to provide a GenerateOneTimeTokenRequestResolver that can be used in the GenerateOneTimeTokenFilter, something like:

This may be a good solution, but I would first look at the design of the OneTimeTokenService. It seems to me that two components for generation a token and storing it would give much more flexibility in the API, as well as the single responsibility principle.
Overall, separating the token generation logic from its storage will make the API simpler, cleaner, and easier to use.

@franticticktick
Copy link
Contributor Author

Can you elaborate more on your use case? Would you like to authenticate the session that started the request instead of the session that submits the token?

I'm more interested in the ability of the OneTimeTokenService to work in a distributed environment. Maybe JbcOneTimeTokenService or CachedOneTimeTokenService? Something like this.

@marcusdacoregio
Copy link
Contributor

I'm unsure because OneTimeTokenService#consume is like a delete operation. I don't think I'd like to see:

this.oneTimeTokenService.consume(ott);
this.oneTimeTokenRepository.delete(ott);

What if the token value is a JWT, for example? It wouldn't have to be saved anywhere and the repository would be a no-op.

@marcusdacoregio marcusdacoregio removed their assignment Sep 17, 2024
@franticticktick
Copy link
Contributor Author

@marcusdacoregio
This may make sense, I would like to discuss the idea of ​​​​implementing OneTimeTokenService to be able to work with multiple instances of the same application. What do you think about it?

@rwinch
Copy link
Member

rwinch commented Sep 23, 2024

@CrazyParanoid I think that it makes sense for us to provide a OneTimeTokenService that works in multiple instances. I'm not keen on the idea of using a JWT because the lack of state means that it can be reused and isn't really a one time token. I think creating a JdbcOneTimeTokenService makes the most sense. If we need to, some of the shared logic can be extracted from InMemoryOneTimeTokenService and shared with JdbcOneTimeTokenService. Is this something you would be interested in submitting a Pull Request for?

@franticticktick
Copy link
Contributor Author

Is this something you would be interested in submitting a Pull Request for?

Yes, I'll take it to work. I'll think about how best to extract the shared code, maybe something like:

public final class OneTimeTokenUtils {
	public static long DEFAULT_ONE_TIME_TOKEN_TIME_TO_LIVE = 300;

	private static Clock clock = Clock.systemUTC();

	private OneTimeTokenUtils() {
	}

	public static OneTimeToken generateOneTimeToken(GenerateOneTimeTokenRequest request, long timeToLive) {
		String token = UUID.randomUUID().toString();
		Instant fiveMinutesFromNow = clock.instant().plusSeconds(timeToLive);
		return new DefaultOneTimeToken(token, request.getUsername(), fiveMinutesFromNow);
	}

	public static boolean isExpired(OneTimeToken token) {
		return clock.instant().isAfter(token.getExpiresAt());
	}
}

Or something like this.

franticticktick added a commit to franticticktick/spring-security that referenced this issue Sep 24, 2024
franticticktick added a commit to franticticktick/spring-security that referenced this issue Sep 24, 2024
franticticktick added a commit to franticticktick/spring-security that referenced this issue Sep 24, 2024
franticticktick added a commit to franticticktick/spring-security that referenced this issue Sep 24, 2024
franticticktick added a commit to franticticktick/spring-security that referenced this issue Sep 24, 2024
@franticticktick
Copy link
Contributor Author

@marcusdacoregio @rwinch could you please review PR?

@rwinch
Copy link
Member

rwinch commented Sep 27, 2024

@CrazyParanoid Sorry about the delay. I've submitted a review now.

@rwinch rwinch closed this as completed in 50cc36d Oct 2, 2024
rwinch added a commit that referenced this issue Oct 2, 2024
rwinch added a commit that referenced this issue Oct 2, 2024
rwinch added a commit that referenced this issue Oct 2, 2024
Spring Security uses setter methods for optional member variables. Allows
for a null cleanupCron to disable the cleanup.

In a clustered environment it is likely that users do not want all nodes
to be performing a cleanup because it will cause contention on the ott
table.

Another example is if a user wants to invoke cleanUpExpiredTokens with a
different strategy all together, they might want to disable the cron job.

Issue gh-15735
rwinch added a commit that referenced this issue Oct 2, 2024
This improves readability.

Issue gh-15735
rwinch added a commit that referenced this issue Oct 2, 2024
rwinch added a commit that referenced this issue Oct 2, 2024
rwinch added a commit that referenced this issue Oct 2, 2024
sjohnr added a commit that referenced this issue Oct 18, 2024
@jzheaux jzheaux changed the title Consider adding OneTimeTokenService for a clustered environment Support One-Time Tokens in a Clustered Environment Oct 24, 2024
@jzheaux jzheaux added the in: web An issue in web modules (web, webmvc) label Oct 24, 2024
@jzheaux jzheaux added this to the 6.4.0-RC1 milestone Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants