Skip to content

Commit

Permalink
Consider InMemorySaml2AuthenticationRequestRepository
Browse files Browse the repository at this point in the history
- I wonder about the fact that the repository could fill up with
stale authnrequests and where the balance is with assisting
with cleanup
  • Loading branch information
jzheaux committed Dec 12, 2023
1 parent 3b7c971 commit 8be16a2
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,76 @@ open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository
----
======

=== Storing by Relay State

Instead of storing in the `HttpSession`, you may want to use a sessionless implementation.

For example, you may be using the `SameSite` parameter in your session cookie; in such a case, the browser will not send the SP's session cookie from the IdP's POST.

In this case, `InMemorySaml2AuthenticationRequestRepository` may be of value since it will store ``<saml2:AuthnRequest>``s by the relay state parameter instead.

You can use it in the following way:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository() {
return new InMemorySaml2AuthenticationRequestRepository();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {
return CustomSaml2AuthenticationRequestRepository()
}
----
======

making sure to introduce a bean that will periodically clean up stale ``<saml2:AuthnRequest>`` instances like so:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Component
public class InMemorySaml2AuthenticationRequestRepositoryEvicter {
private final InMemorySaml2AuthenticationRequestRepository requests;
public InMemorySaml2AuthenticationRequestRepositoryEvicter(InMemorySaml2AuthenticationRequestRepository requests) {
this.requests = requests;
}
@Scheduled
public void evict() {
this.requests.removeAuthenticationRequestsOlderThan(Instant.now().minusSeconds(600));
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Component
class InMemorySaml2AuthenticationRequestRepositoryEvicter(requests: InMemorySaml2AuthenticationRequestRepository) {
@Scheduled
fun evict() {
this.requests.removeAuthenticationRequestsOlderThan(Instant.now().minusSeconds(600))
}
}
----
======

[[servlet-saml2login-sp-initiated-factory-signing]]
== Changing How the `<saml2:AuthnRequest>` Gets Sent

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.saml2.provider.service.web;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;

/**
* A {@link Saml2AuthenticationRequestRepository} that stores all AuthnRequests in-memory.
*
* <p>
* Note that because these values are in-memory, stale requests can accumulate due to
* abandoned login attempts. As such, you should also have a process that performs a
* periodic cleanup. You can do this with Spring with relative ease:
*
* <pre>
* &#64;Component
* public class InMemorySaml2AuthenticationRequestRepositoryEvicter {
* private final InMemorySaml2AuthenticationRequestRepository requests;
*
* &#64;Scheduled
* public void evict() {
* this.request.removeAuthenticationRequestsOlderThan(Instant.now().minusSeconds(600));
* }
* }
* </pre>
*
* @author Josh Cummings
* @since 6.3
*/
public final class InMemorySaml2AuthenticationRequestRepository
implements Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> {

private final Map<String, Entry> byRelayState = new ConcurrentHashMap<>();

/**
* {@inheritDoc}
*/
@Override
public AbstractSaml2AuthenticationRequest loadAuthenticationRequest(HttpServletRequest request) {
String relayState = request.getParameter(Saml2ParameterNames.RELAY_STATE);
if (this.byRelayState.containsKey(relayState)) {
return this.byRelayState.get(relayState).request;
}
return null;
}

/**
* {@inheritDoc}
*/
@Override
public void saveAuthenticationRequest(AbstractSaml2AuthenticationRequest authenticationRequest,
HttpServletRequest request, HttpServletResponse response) {
this.byRelayState.put(authenticationRequest.getRelayState(), new Entry(authenticationRequest, Instant.now()));
}

/**
* {@inheritDoc}
*/
@Override
public AbstractSaml2AuthenticationRequest removeAuthenticationRequest(HttpServletRequest request,
HttpServletResponse response) {
String relayState = request.getParameter(Saml2ParameterNames.RELAY_STATE);
if (this.byRelayState.containsKey(relayState)) {
return this.byRelayState.remove(relayState).request;
}
return null;
}

/**
* Remove any authentication requests that have been in-memory longer than the given
* {@code time}
* @param time the expiring value to apply
*/
public void removeAuthenticationRequestsOlderThan(Instant time) {
Collection<String> toEvict = new ArrayList<>();
for (Map.Entry<String, Entry> entry : this.byRelayState.entrySet()) {
if (entry.getValue().added.isBefore(time)) {
toEvict.add(entry.getKey());
}
}
for (String key : toEvict) {
this.byRelayState.remove(key);
}
}

private record Entry(AbstractSaml2AuthenticationRequest request, Instant added) {

}

}

0 comments on commit 8be16a2

Please sign in to comment.