|
@@ -27,7 +27,10 @@ import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
|
import org.springframework.http.HttpHeaders;
|
|
|
import org.springframework.http.MediaType;
|
|
|
+import org.springframework.security.saml2.Saml2Exception;
|
|
|
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver;
|
|
|
+import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
|
|
|
+import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver;
|
|
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
|
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
@@ -46,27 +49,20 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
|
|
|
|
|
|
public static final String DEFAULT_METADATA_FILE_NAME = "saml-{registrationId}-metadata.xml";
|
|
|
|
|
|
- private final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver;
|
|
|
-
|
|
|
- private final Saml2MetadataResolver saml2MetadataResolver;
|
|
|
-
|
|
|
- private String metadataFilename = DEFAULT_METADATA_FILE_NAME;
|
|
|
-
|
|
|
- private RequestMatcher requestMatcher = new AntPathRequestMatcher(
|
|
|
- "/saml2/service-provider-metadata/{registrationId}");
|
|
|
+ private final Saml2MetadataResponseResolver metadataResolver;
|
|
|
|
|
|
public Saml2MetadataFilter(RelyingPartyRegistrationResolver relyingPartyRegistrationResolver,
|
|
|
Saml2MetadataResolver saml2MetadataResolver) {
|
|
|
Assert.notNull(relyingPartyRegistrationResolver, "relyingPartyRegistrationResolver cannot be null");
|
|
|
Assert.notNull(saml2MetadataResolver, "saml2MetadataResolver cannot be null");
|
|
|
- this.relyingPartyRegistrationResolver = relyingPartyRegistrationResolver;
|
|
|
- this.saml2MetadataResolver = saml2MetadataResolver;
|
|
|
+ this.metadataResolver = new Saml2MetadataResponseResolverAdapter(relyingPartyRegistrationResolver,
|
|
|
+ saml2MetadataResolver);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Constructs an instance of {@link Saml2MetadataFilter} using the provided
|
|
|
- * parameters. The {@link #relyingPartyRegistrationResolver} field will be initialized
|
|
|
- * with a {@link DefaultRelyingPartyRegistrationResolver} instance using the provided
|
|
|
+ * parameters. The {@link #metadataResolver} field will be initialized with a
|
|
|
+ * {@link DefaultRelyingPartyRegistrationResolver} instance using the provided
|
|
|
* {@link RelyingPartyRegistrationRepository}
|
|
|
* @param relyingPartyRegistrationRepository the
|
|
|
* {@link RelyingPartyRegistrationRepository} to use
|
|
@@ -78,35 +74,43 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
|
|
|
this(new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository), saml2MetadataResolver);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Constructs an instance of {@link Saml2MetadataFilter}
|
|
|
+ * @param metadataResponseResolver the strategy for producing metadata
|
|
|
+ * @since 6.1
|
|
|
+ */
|
|
|
+ public Saml2MetadataFilter(Saml2MetadataResponseResolver metadataResponseResolver) {
|
|
|
+ this.metadataResolver = metadataResponseResolver;
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
|
|
throws ServletException, IOException {
|
|
|
- RequestMatcher.MatchResult matcher = this.requestMatcher.matcher(request);
|
|
|
- if (!matcher.isMatch()) {
|
|
|
- chain.doFilter(request, response);
|
|
|
- return;
|
|
|
+ Saml2MetadataResponse metadata;
|
|
|
+ try {
|
|
|
+ metadata = this.metadataResolver.resolve(request);
|
|
|
}
|
|
|
- String registrationId = matcher.getVariables().get("registrationId");
|
|
|
- RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationResolver.resolve(request,
|
|
|
- registrationId);
|
|
|
- if (relyingPartyRegistration == null) {
|
|
|
+ catch (Saml2Exception ex) {
|
|
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
|
return;
|
|
|
}
|
|
|
- String metadata = this.saml2MetadataResolver.resolve(relyingPartyRegistration);
|
|
|
- writeMetadataToResponse(response, relyingPartyRegistration.getRegistrationId(), metadata);
|
|
|
+ if (metadata == null) {
|
|
|
+ chain.doFilter(request, response);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ writeMetadataToResponse(response, metadata);
|
|
|
}
|
|
|
|
|
|
- private void writeMetadataToResponse(HttpServletResponse response, String registrationId, String metadata)
|
|
|
+ private void writeMetadataToResponse(HttpServletResponse response, Saml2MetadataResponse metadata)
|
|
|
throws IOException {
|
|
|
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
|
|
- String fileName = this.metadataFilename.replace("{registrationId}", registrationId);
|
|
|
- String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
|
|
|
String format = "attachment; filename=\"%s\"; filename*=UTF-8''%s";
|
|
|
+ String fileName = metadata.getFileName();
|
|
|
+ String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
|
|
|
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format(format, fileName, encodedFileName));
|
|
|
- response.setContentLength(metadata.length());
|
|
|
+ response.setContentLength(metadata.getMetadata().length());
|
|
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
|
|
- response.getWriter().write(metadata);
|
|
|
+ response.getWriter().write(metadata.getMetadata());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -116,7 +120,9 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
|
|
|
*/
|
|
|
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
|
|
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
|
|
- this.requestMatcher = requestMatcher;
|
|
|
+ Assert.isInstanceOf(Saml2MetadataResponseResolverAdapter.class, this.metadataResolver,
|
|
|
+ "a Saml2MetadataResponseResolver and RequestMatcher cannot be both set on this filter. Please set the request matcher on the Saml2MetadataResponseResolver itself.");
|
|
|
+ ((Saml2MetadataResponseResolverAdapter) this.metadataResolver).setRequestMatcher(requestMatcher);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -132,7 +138,57 @@ public final class Saml2MetadataFilter extends OncePerRequestFilter {
|
|
|
Assert.hasText(metadataFilename, "metadataFilename cannot be empty");
|
|
|
Assert.isTrue(metadataFilename.contains("{registrationId}"),
|
|
|
"metadataFilename must contain a {registrationId} match variable");
|
|
|
- this.metadataFilename = metadataFilename;
|
|
|
+ Assert.isInstanceOf(Saml2MetadataResponseResolverAdapter.class, this.metadataResolver,
|
|
|
+ "a Saml2MetadataResponseResolver and file name cannot be both set on this filter. Please set the file name on the Saml2MetadataResponseResolver itself.");
|
|
|
+ ((Saml2MetadataResponseResolverAdapter) this.metadataResolver).setMetadataFilename(metadataFilename);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final class Saml2MetadataResponseResolverAdapter implements Saml2MetadataResponseResolver {
|
|
|
+
|
|
|
+ private final RelyingPartyRegistrationResolver registrations;
|
|
|
+
|
|
|
+ private RequestMatcher requestMatcher = new AntPathRequestMatcher(
|
|
|
+ "/saml2/service-provider-metadata/{registrationId}");
|
|
|
+
|
|
|
+ private final Saml2MetadataResolver metadataResolver;
|
|
|
+
|
|
|
+ private String metadataFilename = DEFAULT_METADATA_FILE_NAME;
|
|
|
+
|
|
|
+ Saml2MetadataResponseResolverAdapter(RelyingPartyRegistrationResolver registrations,
|
|
|
+ Saml2MetadataResolver metadataResolver) {
|
|
|
+ this.registrations = registrations;
|
|
|
+ this.metadataResolver = metadataResolver;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Saml2MetadataResponse resolve(HttpServletRequest request) {
|
|
|
+ RequestMatcher.MatchResult matcher = this.requestMatcher.matcher(request);
|
|
|
+ if (!matcher.isMatch()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String registrationId = matcher.getVariables().get("registrationId");
|
|
|
+ RelyingPartyRegistration relyingPartyRegistration = this.registrations.resolve(request, registrationId);
|
|
|
+ if (relyingPartyRegistration == null) {
|
|
|
+ throw new Saml2Exception("registration not found");
|
|
|
+ }
|
|
|
+ registrationId = relyingPartyRegistration.getRegistrationId();
|
|
|
+ String metadata = this.metadataResolver.resolve(relyingPartyRegistration);
|
|
|
+ String fileName = this.metadataFilename.replace("{registrationId}", registrationId);
|
|
|
+ return new Saml2MetadataResponse(metadata, fileName);
|
|
|
+ }
|
|
|
+
|
|
|
+ void setRequestMatcher(RequestMatcher requestMatcher) {
|
|
|
+ Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
|
|
+ this.requestMatcher = requestMatcher;
|
|
|
+ }
|
|
|
+
|
|
|
+ void setMetadataFilename(String metadataFilename) {
|
|
|
+ Assert.hasText(metadataFilename, "metadataFilename cannot be empty");
|
|
|
+ Assert.isTrue(metadataFilename.contains("{registrationId}"),
|
|
|
+ "metadataFilename must contain a {registrationId} match variable");
|
|
|
+ this.metadataFilename = metadataFilename;
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|