@@ -104,8 +104,57 @@ private ClientRegistrations() {
104
104
* Provider Configuration.
105
105
*/
106
106
public static ClientRegistration .Builder fromOidcIssuerLocation (String issuer ) {
107
+ return fromOidcIssuerLocation (issuer , issuer );
108
+ }
109
+
110
+ /**
111
+ * Creates a {@link ClientRegistration.Builder} using the provided <a href=
112
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
113
+ * by making an <a href=
114
+ * "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">OpenID
115
+ * Provider Configuration Request</a> and using the values in the <a href=
116
+ * "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">OpenID
117
+ * Provider Configuration Response</a> to initialize the
118
+ * {@link ClientRegistration.Builder}.
119
+ *
120
+ * <p>
121
+ * This is a specialized overload of {@link #fromOidcIssuerLocation(String)} that
122
+ * allows to fetch metadata from a different issuer than the one that is expected to
123
+ * be declared in the metadata, for example when the application can only communicate
124
+ * with the issuer using a backend URL.
125
+ * </p>
126
+ *
127
+ * <p>
128
+ * For example, if the issuer provided is "https://backend-issuer.com" and the
129
+ * expected metadata issuer is "https://frontend-issuer.com", then an "OpenID Provider
130
+ * Configuration Request" will be made to
131
+ * "https://backend-issuer.com/.well-known/openid-configuration" and the returning
132
+ * metadata is expected to declare "https://frontend-issuer.com" in the issuer field.
133
+ * The result is expected to be an "OpenID Provider Configuration Response".
134
+ * </p>
135
+ *
136
+ * <p>
137
+ * Example usage:
138
+ * </p>
139
+ * <pre>
140
+ * ClientRegistration registration = ClientRegistrations
141
+ * .fromOidcIssuerLocation("https://backend-issuer.com", "https://frontend-issuer.com")
142
+ * .clientId("client-id")
143
+ * .clientSecret("client-secret")
144
+ * .build();
145
+ * </pre>
146
+ * @param issuer the <a href=
147
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
148
+ * @param expectedIssuer the expected <a href=
149
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
150
+ * to use for metadata validation
151
+ * @return a {@link ClientRegistration.Builder} that was initialized by the OpenID
152
+ * Provider Configuration.
153
+ */
154
+ public static ClientRegistration .Builder fromOidcIssuerLocation (String issuer , String expectedIssuer ) {
107
155
Assert .hasText (issuer , "issuer cannot be empty" );
108
- return getBuilder (issuer , oidc (URI .create (issuer )));
156
+ Assert .hasText (expectedIssuer , "expectedIssuer cannot be empty" );
157
+ return getBuilder (issuer , oidc (URI .create (issuer ), URI .create (expectedIssuer )));
109
158
}
110
159
111
160
/**
@@ -147,12 +196,68 @@ public static ClientRegistration.Builder fromOidcIssuerLocation(String issuer) {
147
196
* described endpoints
148
197
*/
149
198
public static ClientRegistration .Builder fromIssuerLocation (String issuer ) {
199
+ return fromIssuerLocation (issuer , issuer );
200
+ }
201
+
202
+ /**
203
+ * Creates a {@link ClientRegistration.Builder} using the provided <a href=
204
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
205
+ * by querying three different discovery endpoints serially, using the values in the
206
+ * first successful response to initialize. If an endpoint returns anything other than
207
+ * a 200 or a 4xx, the method will exit without attempting subsequent endpoints.
208
+ *
209
+ * The three endpoints are computed as follows, given that the {@code issuer} is
210
+ * composed of a {@code host} and a {@code path}:
211
+ *
212
+ * <ol>
213
+ * <li>{@code host/.well-known/openid-configuration/path}, as defined in
214
+ * <a href="https://tools.ietf.org/html/rfc8414#section-5">RFC 8414's Compatibility
215
+ * Notes</a>.</li>
216
+ * <li>{@code issuer/.well-known/openid-configuration}, as defined in <a href=
217
+ * "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">
218
+ * OpenID Provider Configuration</a>.</li>
219
+ * <li>{@code host/.well-known/oauth-authorization-server/path}, as defined in
220
+ * <a href="https://tools.ietf.org/html/rfc8414#section-3.1">Authorization Server
221
+ * Metadata Request</a>.</li>
222
+ * </ol>
223
+ *
224
+ * Note that the second endpoint is the equivalent of calling
225
+ * {@link ClientRegistrations#fromOidcIssuerLocation(String)}.
226
+ *
227
+ * <p>
228
+ * This is a specialized overload of {@link #fromIssuerLocation(String)} that allows
229
+ * to fetch metadata from a different issuer than the one that is expected to be
230
+ * declared in the metadata, for example when the application can only communicate
231
+ * with the issuer using a backend URL.
232
+ * </p>
233
+ *
234
+ * <p>
235
+ * Example usage:
236
+ * </p>
237
+ * <pre>
238
+ * ClientRegistration registration = ClientRegistrations
239
+ * .fromIssuerLocation("https://backend-example.com", "https://frontend-example.com")
240
+ * .clientId("client-id")
241
+ * .clientSecret("client-secret")
242
+ * .build();
243
+ * </pre>
244
+ * @param issuer the <a href=
245
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
246
+ * @param expectedIssuer the expected <a href=
247
+ * "https://openid.net/specs/openid-connect-core-1_0.html#IssuerIdentifier">Issuer</a>
248
+ * to use for metadata validation
249
+ * @return a {@link ClientRegistration.Builder} that was initialized by one of the
250
+ * described endpoints
251
+ */
252
+ public static ClientRegistration .Builder fromIssuerLocation (String issuer , String expectedIssuer ) {
150
253
Assert .hasText (issuer , "issuer cannot be empty" );
254
+ Assert .hasText (expectedIssuer , "expectedIssuer cannot be empty" );
151
255
URI uri = URI .create (issuer );
152
- return getBuilder (issuer , oidc (uri ), oidcRfc8414 (uri ), oauth (uri ));
256
+ URI expectedUri = URI .create (expectedIssuer );
257
+ return getBuilder (issuer , oidc (uri , expectedUri ), oidcRfc8414 (uri , expectedUri ), oauth (uri , expectedUri ));
153
258
}
154
259
155
- private static Supplier <ClientRegistration .Builder > oidc (URI issuer ) {
260
+ private static Supplier <ClientRegistration .Builder > oidc (URI issuer , URI expectedIssuer ) {
156
261
// @formatter:off
157
262
URI uri = UriComponentsBuilder .fromUri (issuer )
158
263
.replacePath (issuer .getPath () + OIDC_METADATA_PATH )
@@ -162,7 +267,7 @@ private static Supplier<ClientRegistration.Builder> oidc(URI issuer) {
162
267
RequestEntity <Void > request = RequestEntity .get (uri ).build ();
163
268
Map <String , Object > configuration = rest .exchange (request , typeReference ).getBody ();
164
269
OIDCProviderMetadata metadata = parse (configuration , OIDCProviderMetadata ::parse );
165
- ClientRegistration .Builder builder = withProviderConfiguration (metadata , issuer .toASCIIString ())
270
+ ClientRegistration .Builder builder = withProviderConfiguration (metadata , expectedIssuer .toASCIIString ())
166
271
.jwkSetUri (metadata .getJWKSetURI ().toASCIIString ());
167
272
if (metadata .getUserInfoEndpointURI () != null ) {
168
273
builder .userInfoUri (metadata .getUserInfoEndpointURI ().toASCIIString ());
@@ -171,30 +276,30 @@ private static Supplier<ClientRegistration.Builder> oidc(URI issuer) {
171
276
};
172
277
}
173
278
174
- private static Supplier <ClientRegistration .Builder > oidcRfc8414 (URI issuer ) {
279
+ private static Supplier <ClientRegistration .Builder > oidcRfc8414 (URI issuer , URI expectedIssuer ) {
175
280
// @formatter:off
176
281
URI uri = UriComponentsBuilder .fromUri (issuer )
177
282
.replacePath (OIDC_METADATA_PATH + issuer .getPath ())
178
283
.build (Collections .emptyMap ());
179
284
// @formatter:on
180
- return getRfc8414Builder (issuer , uri );
285
+ return getRfc8414Builder (issuer , uri , expectedIssuer );
181
286
}
182
287
183
- private static Supplier <ClientRegistration .Builder > oauth (URI issuer ) {
288
+ private static Supplier <ClientRegistration .Builder > oauth (URI issuer , URI expectedIssuer ) {
184
289
// @formatter:off
185
290
URI uri = UriComponentsBuilder .fromUri (issuer )
186
291
.replacePath (OAUTH_METADATA_PATH + issuer .getPath ())
187
292
.build (Collections .emptyMap ());
188
293
// @formatter:on
189
- return getRfc8414Builder (issuer , uri );
294
+ return getRfc8414Builder (issuer , uri , expectedIssuer );
190
295
}
191
296
192
- private static Supplier <ClientRegistration .Builder > getRfc8414Builder (URI issuer , URI uri ) {
297
+ private static Supplier <ClientRegistration .Builder > getRfc8414Builder (URI issuer , URI uri , URI expectedIssuer ) {
193
298
return () -> {
194
299
RequestEntity <Void > request = RequestEntity .get (uri ).build ();
195
300
Map <String , Object > configuration = rest .exchange (request , typeReference ).getBody ();
196
301
AuthorizationServerMetadata metadata = parse (configuration , AuthorizationServerMetadata ::parse );
197
- ClientRegistration .Builder builder = withProviderConfiguration (metadata , issuer .toASCIIString ());
302
+ ClientRegistration .Builder builder = withProviderConfiguration (metadata , expectedIssuer .toASCIIString ());
198
303
URI jwkSetUri = metadata .getJWKSetURI ();
199
304
if (jwkSetUri != null ) {
200
305
builder .jwkSetUri (jwkSetUri .toASCIIString ());
0 commit comments