Skip to content

Commit 16fd24c

Browse files
chao.wangjzheaux
authored andcommitted
Add JdbcAssertingPartyMetadataRepository
Closes gh-16012 Signed-off-by: chao.wang <[email protected]>
1 parent 9b72437 commit 16fd24c

File tree

6 files changed

+420
-0
lines changed

6 files changed

+420
-0
lines changed

saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ dependencies {
106106
provided 'jakarta.servlet:jakarta.servlet-api'
107107

108108
optional 'com.fasterxml.jackson.core:jackson-databind'
109+
optional 'org.springframework:spring-jdbc'
109110

110111
testImplementation 'com.squareup.okhttp3:mockwebserver'
111112
testImplementation "org.assertj:assertj-core"
@@ -118,6 +119,7 @@ dependencies {
118119
testImplementation "org.springframework:spring-test"
119120

120121
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
122+
testRuntimeOnly 'org.hsqldb:hsqldb'
121123
}
122124

123125
jar {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.registration;
18+
19+
import java.sql.ResultSet;
20+
import java.sql.SQLException;
21+
import java.sql.Types;
22+
import java.util.Collection;
23+
import java.util.Iterator;
24+
import java.util.List;
25+
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
28+
29+
import org.springframework.core.log.LogMessage;
30+
import org.springframework.core.serializer.DefaultDeserializer;
31+
import org.springframework.core.serializer.Deserializer;
32+
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
33+
import org.springframework.jdbc.core.JdbcOperations;
34+
import org.springframework.jdbc.core.PreparedStatementSetter;
35+
import org.springframework.jdbc.core.RowMapper;
36+
import org.springframework.jdbc.core.SqlParameterValue;
37+
import org.springframework.security.saml2.core.Saml2X509Credential;
38+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
39+
import org.springframework.util.Assert;
40+
41+
/**
42+
* A JDBC implementation of {@link AssertingPartyMetadataRepository}.
43+
*
44+
* @author Cathy Wang
45+
* @since 7.0
46+
*/
47+
public final class JdbcAssertingPartyMetadataRepository implements AssertingPartyMetadataRepository {
48+
49+
private final JdbcOperations jdbcOperations;
50+
51+
private RowMapper<AssertingPartyMetadata> assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper(
52+
ResultSet::getBytes);
53+
54+
// @formatter:off
55+
static final String COLUMN_NAMES = "entity_id, "
56+
+ "singlesignon_url, "
57+
+ "singlesignon_binding, "
58+
+ "singlesignon_sign_request, "
59+
+ "signing_algorithms, "
60+
+ "verification_credentials, "
61+
+ "encryption_credentials, "
62+
+ "singlelogout_url, "
63+
+ "singlelogout_response_url, "
64+
+ "singlelogout_binding";
65+
// @formatter:on
66+
67+
private static final String TABLE_NAME = "saml2_asserting_party_metadata";
68+
69+
private static final String ENTITY_ID_FILTER = "entity_id = ?";
70+
71+
// @formatter:off
72+
private static final String LOAD_BY_ID_SQL = "SELECT " + COLUMN_NAMES
73+
+ " FROM " + TABLE_NAME
74+
+ " WHERE " + ENTITY_ID_FILTER;
75+
76+
private static final String LOAD_ALL_SQL = "SELECT " + COLUMN_NAMES
77+
+ " FROM " + TABLE_NAME;
78+
// @formatter:on
79+
80+
/**
81+
* Constructs a {@code JdbcRelyingPartyRegistrationRepository} using the provided
82+
* parameters.
83+
* @param jdbcOperations the JDBC operations
84+
*/
85+
public JdbcAssertingPartyMetadataRepository(JdbcOperations jdbcOperations) {
86+
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
87+
this.jdbcOperations = jdbcOperations;
88+
}
89+
90+
/**
91+
* Sets the {@link RowMapper} used for mapping the current row in
92+
* {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}. The default is
93+
* {@link AssertingPartyMetadataRowMapper}.
94+
* @param assertingPartyMetadataRowMapper the {@link RowMapper} used for mapping the
95+
* current row in {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}
96+
*/
97+
public void setAssertingPartyMetadataRowMapper(RowMapper<AssertingPartyMetadata> assertingPartyMetadataRowMapper) {
98+
Assert.notNull(assertingPartyMetadataRowMapper, "assertingPartyMetadataRowMapper cannot be null");
99+
this.assertingPartyMetadataRowMapper = assertingPartyMetadataRowMapper;
100+
}
101+
102+
@Override
103+
public AssertingPartyMetadata findByEntityId(String entityId) {
104+
Assert.hasText(entityId, "entityId cannot be empty");
105+
SqlParameterValue[] parameters = new SqlParameterValue[] { new SqlParameterValue(Types.VARCHAR, entityId) };
106+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
107+
List<AssertingPartyMetadata> result = this.jdbcOperations.query(LOAD_BY_ID_SQL, pss,
108+
this.assertingPartyMetadataRowMapper);
109+
return !result.isEmpty() ? result.get(0) : null;
110+
}
111+
112+
@Override
113+
public Iterator<AssertingPartyMetadata> iterator() {
114+
List<AssertingPartyMetadata> result = this.jdbcOperations.query(LOAD_ALL_SQL,
115+
this.assertingPartyMetadataRowMapper);
116+
return result.iterator();
117+
}
118+
119+
/**
120+
* The default {@link RowMapper} that maps the current row in
121+
* {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}.
122+
*/
123+
private static final class AssertingPartyMetadataRowMapper implements RowMapper<AssertingPartyMetadata> {
124+
125+
private final Log logger = LogFactory.getLog(AssertingPartyMetadataRowMapper.class);
126+
127+
private final Deserializer<Object> deserializer = new DefaultDeserializer();
128+
129+
private final GetBytes getBytes;
130+
131+
AssertingPartyMetadataRowMapper(GetBytes getBytes) {
132+
this.getBytes = getBytes;
133+
}
134+
135+
@Override
136+
public AssertingPartyMetadata mapRow(ResultSet rs, int rowNum) throws SQLException {
137+
String entityId = rs.getString("entity_id");
138+
String singleSignOnUrl = rs.getString("singlesignon_url");
139+
Saml2MessageBinding singleSignOnBinding = Saml2MessageBinding.from(rs.getString("singlesignon_binding"));
140+
boolean singleSignOnSignRequest = rs.getBoolean("singlesignon_sign_request");
141+
String singleLogoutUrl = rs.getString("singlelogout_url");
142+
String singleLogoutResponseUrl = rs.getString("singlelogout_response_url");
143+
Saml2MessageBinding singleLogoutBinding = Saml2MessageBinding.from(rs.getString("singlelogout_binding"));
144+
byte[] signingAlgorithmsBytes = this.getBytes.getBytes(rs, "signing_algorithms");
145+
byte[] verificationCredentialsBytes = this.getBytes.getBytes(rs, "verification_credentials");
146+
byte[] encryptionCredentialsBytes = this.getBytes.getBytes(rs, "encryption_credentials");
147+
148+
AssertingPartyMetadata.Builder<?> builder = new AssertingPartyDetails.Builder();
149+
try {
150+
if (signingAlgorithmsBytes != null) {
151+
List<String> signingAlgorithms = (List<String>) this.deserializer
152+
.deserializeFromByteArray(signingAlgorithmsBytes);
153+
builder.signingAlgorithms((algorithms) -> algorithms.addAll(signingAlgorithms));
154+
}
155+
if (verificationCredentialsBytes != null) {
156+
Collection<Saml2X509Credential> verificationCredentials = (Collection<Saml2X509Credential>) this.deserializer
157+
.deserializeFromByteArray(verificationCredentialsBytes);
158+
builder.verificationX509Credentials((credentials) -> credentials.addAll(verificationCredentials));
159+
}
160+
if (encryptionCredentialsBytes != null) {
161+
Collection<Saml2X509Credential> encryptionCredentials = (Collection<Saml2X509Credential>) this.deserializer
162+
.deserializeFromByteArray(encryptionCredentialsBytes);
163+
builder.encryptionX509Credentials((credentials) -> credentials.addAll(encryptionCredentials));
164+
}
165+
}
166+
catch (Exception ex) {
167+
this.logger.debug(LogMessage.format("Parsing serialized credentials for entity %s failed", entityId),
168+
ex);
169+
return null;
170+
}
171+
172+
builder.entityId(entityId)
173+
.wantAuthnRequestsSigned(singleSignOnSignRequest)
174+
.singleSignOnServiceLocation(singleSignOnUrl)
175+
.singleSignOnServiceBinding(singleSignOnBinding)
176+
.singleLogoutServiceLocation(singleLogoutUrl)
177+
.singleLogoutServiceBinding(singleLogoutBinding)
178+
.singleLogoutServiceResponseLocation(singleLogoutResponseUrl);
179+
return builder.build();
180+
}
181+
182+
}
183+
184+
private interface GetBytes {
185+
186+
byte[] getBytes(ResultSet rs, String columnName) throws SQLException;
187+
188+
}
189+
190+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE saml2_asserting_party_metadata
2+
(
3+
entity_id VARCHAR(1000) NOT NULL,
4+
singlesignon_url VARCHAR(1000) NOT NULL,
5+
singlesignon_binding VARCHAR(100),
6+
singlesignon_sign_request boolean,
7+
signing_algorithms BYTEA,
8+
verification_credentials BYTEA NOT NULL,
9+
encryption_credentials BYTEA,
10+
singlelogout_url VARCHAR(1000),
11+
singlelogout_response_url VARCHAR(1000),
12+
singlelogout_binding VARCHAR(100),
13+
PRIMARY KEY (entity_id)
14+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CREATE TABLE saml2_asserting_party_metadata
2+
(
3+
entity_id VARCHAR(1000) NOT NULL,
4+
singlesignon_url VARCHAR(1000) NOT NULL,
5+
singlesignon_binding VARCHAR(100),
6+
singlesignon_sign_request boolean,
7+
signing_algorithms blob,
8+
verification_credentials blob NOT NULL,
9+
encryption_credentials blob,
10+
singlelogout_url VARCHAR(1000),
11+
singlelogout_response_url VARCHAR(1000),
12+
singlelogout_binding VARCHAR(100),
13+
PRIMARY KEY (entity_id)
14+
);

0 commit comments

Comments
 (0)