Browse Source

Set iss claim in Jwt using configured issuer

Closes gh-223
Joe Grandja 4 years ago
parent
commit
c9afc3e061

+ 0 - 123
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtEncodingContextUtils.java

@@ -1,123 +0,0 @@
-/*
- * Copyright 2020-2021 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.oauth2.server.authorization.authentication;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Collections;
-import java.util.Set;
-
-import org.springframework.security.oauth2.core.OAuth2TokenType;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
-import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
-import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
-import org.springframework.security.oauth2.jwt.JoseHeader;
-import org.springframework.security.oauth2.jwt.JwtClaimsSet;
-import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
-
-/**
- * @author Joe Grandja
- * @since 0.1.0
- */
-final class JwtEncodingContextUtils {
-	private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
-
-	private JwtEncodingContextUtils() {
-	}
-
-	static JwtEncodingContext.Builder accessTokenContext(RegisteredClient registeredClient, OAuth2Authorization authorization) {
-		// @formatter:off
-		return accessTokenContext(registeredClient, authorization,
-				authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
-		// @formatter:on
-	}
-
-	static JwtEncodingContext.Builder accessTokenContext(RegisteredClient registeredClient, OAuth2Authorization authorization,
-			Set<String> authorizedScopes) {
-		// @formatter:off
-		return accessTokenContext(registeredClient, authorization.getPrincipalName(), authorizedScopes)
-				.authorization(authorization);
-		// @formatter:on
-	}
-
-	static JwtEncodingContext.Builder accessTokenContext(RegisteredClient registeredClient,
-			String principalName, Set<String> authorizedScopes) {
-
-		JoseHeader.Builder headersBuilder = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256);
-
-		String issuer = "http://auth-server:9000";        // TODO Allow configuration for issuer claim
-		Instant issuedAt = Instant.now();
-		Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().accessTokenTimeToLive());
-
-		// @formatter:off
-		JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
-				.issuer(issuer)
-				.subject(principalName)
-				.audience(Collections.singletonList(registeredClient.getClientId()))
-				.issuedAt(issuedAt)
-				.expiresAt(expiresAt)
-				.notBefore(issuedAt);
-		if (!CollectionUtils.isEmpty(authorizedScopes)) {
-			claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
-		}
-		// @formatter:on
-
-		// @formatter:off
-		return JwtEncodingContext.with(headersBuilder, claimsBuilder)
-				.registeredClient(registeredClient)
-				.tokenType(OAuth2TokenType.ACCESS_TOKEN);
-		// @formatter:on
-	}
-
-	static JwtEncodingContext.Builder idTokenContext(RegisteredClient registeredClient, OAuth2Authorization authorization) {
-		JoseHeader.Builder headersBuilder = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256);
-
-		String issuer = "http://auth-server:9000";        // TODO Allow configuration for issuer claim
-		Instant issuedAt = Instant.now();
-		Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);		// TODO Allow configuration for id token time-to-live
-		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
-				OAuth2AuthorizationRequest.class.getName());
-		String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE);
-
-		// @formatter:off
-		JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder()
-				.issuer(issuer)
-				.subject(authorization.getPrincipalName())
-				.audience(Collections.singletonList(registeredClient.getClientId()))
-				.issuedAt(issuedAt)
-				.expiresAt(expiresAt)
-				.claim(IdTokenClaimNames.AZP, registeredClient.getClientId());
-		if (StringUtils.hasText(nonce)) {
-			claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce);
-		}
-		// TODO Add 'auth_time' claim
-		// @formatter:on
-
-		// @formatter:off
-		return JwtEncodingContext.with(headersBuilder, claimsBuilder)
-				.registeredClient(registeredClient)
-				.authorization(authorization)
-				.tokenType(ID_TOKEN_TOKEN_TYPE);
-		// @formatter:on
-	}
-
-}

+ 101 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtUtils.java

@@ -0,0 +1,101 @@
+/*
+ * Copyright 2020-2021 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.oauth2.server.authorization.authentication;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.oauth2.jwt.JoseHeader;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Utility methods used by the {@link AuthenticationProvider}'s when issuing {@link Jwt}'s.
+ *
+ * @author Joe Grandja
+ * @since 0.1.0
+ */
+final class JwtUtils {
+
+	private JwtUtils() {
+	}
+
+	static JoseHeader.Builder headers() {
+		return JoseHeader.withAlgorithm(SignatureAlgorithm.RS256);
+	}
+
+	static JwtClaimsSet.Builder accessTokenClaims(RegisteredClient registeredClient,
+			String issuer, String subject, Set<String> authorizedScopes) {
+
+		Instant issuedAt = Instant.now();
+		Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().accessTokenTimeToLive());
+
+		// @formatter:off
+		JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
+		if (StringUtils.hasText(issuer)) {
+			claimsBuilder.issuer(issuer);
+		}
+		claimsBuilder
+				.subject(subject)
+				.audience(Collections.singletonList(registeredClient.getClientId()))
+				.issuedAt(issuedAt)
+				.expiresAt(expiresAt)
+				.notBefore(issuedAt);
+		if (!CollectionUtils.isEmpty(authorizedScopes)) {
+			claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
+		}
+		// @formatter:on
+
+		return claimsBuilder;
+	}
+
+	static JwtClaimsSet.Builder idTokenClaims(RegisteredClient registeredClient,
+			String issuer, String subject, String nonce) {
+
+		Instant issuedAt = Instant.now();
+		// TODO Allow configuration for ID Token time-to-live
+		Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
+
+		// @formatter:off
+		JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
+		if (StringUtils.hasText(issuer)) {
+			claimsBuilder.issuer(issuer);
+		}
+		claimsBuilder
+				.subject(subject)
+				.audience(Collections.singletonList(registeredClient.getClientId()))
+				.issuedAt(issuedAt)
+				.expiresAt(expiresAt)
+				.claim(IdTokenClaimNames.AZP, registeredClient.getClientId());
+		if (StringUtils.hasText(nonce)) {
+			claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce);
+		}
+		// TODO Add 'auth_time' claim
+		// @formatter:on
+
+		return claimsBuilder;
+	}
+
+}

+ 37 - 3
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@@ -19,7 +19,9 @@ import java.security.Principal;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -42,6 +44,7 @@ import org.springframework.security.oauth2.jwt.JwtEncoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@@ -66,10 +69,14 @@ import static org.springframework.security.oauth2.server.authorization.authentic
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
  */
 public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
-	private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
+	private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE =
+			new OAuth2TokenType(OAuth2ParameterNames.CODE);
+	private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE =
+			new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
 	private final OAuth2AuthorizationService authorizationService;
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
+	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
@@ -89,6 +96,11 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
 		this.jwtCustomizer = jwtCustomizer;
 	}
 
+	@Autowired(required = false)
+	protected void setProviderSettings(ProviderSettings providerSettings) {
+		this.providerSettings = providerSettings;
+	}
+
 	@Override
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
@@ -127,13 +139,25 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
 		}
 
+		String issuer = this.providerSettings != null ? this.providerSettings.issuer() : null;
+		Set<String> authorizedScopes = authorization.getAttribute(
+				OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
+
+		JoseHeader.Builder headersBuilder = JwtUtils.headers();
+		JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(
+				registeredClient, issuer, authorization.getPrincipalName(), authorizedScopes);
+
 		// @formatter:off
-		JwtEncodingContext context = JwtEncodingContextUtils.accessTokenContext(registeredClient, authorization)
+		JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
+				.registeredClient(registeredClient)
 				.principal(authorization.getAttribute(Principal.class.getName()))
+				.authorization(authorization)
+				.tokenType(OAuth2TokenType.ACCESS_TOKEN)
 				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
 				.authorizationGrant(authorizationCodeAuthentication)
 				.build();
 		// @formatter:on
+
 		this.jwtCustomizer.customize(context);
 
 		JoseHeader headers = context.getHeaders().build();
@@ -152,13 +176,23 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
 
 		Jwt jwtIdToken = null;
 		if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) {
+			String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE);
+
+			headersBuilder = JwtUtils.headers();
+			claimsBuilder = JwtUtils.idTokenClaims(
+					registeredClient, issuer, authorization.getPrincipalName(), nonce);
+
 			// @formatter:off
-			context = JwtEncodingContextUtils.idTokenContext(registeredClient, authorization)
+			context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
+					.registeredClient(registeredClient)
 					.principal(authorization.getAttribute(Principal.class.getName()))
+					.authorization(authorization)
+					.tokenType(ID_TOKEN_TOKEN_TYPE)
 					.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
 					.authorizationGrant(authorizationCodeAuthentication)
 					.build();
 			// @formatter:on
+
 			this.jwtCustomizer.customize(context);
 
 			headers = context.getHeaders().build();

+ 19 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java

@@ -19,6 +19,7 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -27,6 +28,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2TokenType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.jwt.JoseHeader;
 import org.springframework.security.oauth2.jwt.Jwt;
@@ -35,6 +37,7 @@ import org.springframework.security.oauth2.jwt.JwtEncoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
 import org.springframework.util.Assert;
@@ -61,6 +64,7 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
 	private final OAuth2AuthorizationService authorizationService;
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
+	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters.
@@ -81,6 +85,11 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
 		this.jwtCustomizer = jwtCustomizer;
 	}
 
+	@Autowired(required = false)
+	protected void setProviderSettings(ProviderSettings providerSettings) {
+		this.providerSettings = providerSettings;
+	}
+
 	@Override
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
@@ -105,13 +114,22 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
 			scopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
 		}
 
+		String issuer = this.providerSettings != null ? this.providerSettings.issuer() : null;
+
+		JoseHeader.Builder headersBuilder = JwtUtils.headers();
+		JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(
+				registeredClient, issuer, clientPrincipal.getName(), scopes);
+
 		// @formatter:off
-		JwtEncodingContext context = JwtEncodingContextUtils.accessTokenContext(registeredClient, clientPrincipal.getName(), scopes)
+		JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
+				.registeredClient(registeredClient)
 				.principal(clientPrincipal)
+				.tokenType(OAuth2TokenType.ACCESS_TOKEN)
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizationGrant(clientCredentialsAuthentication)
 				.build();
 		// @formatter:on
+
 		this.jwtCustomizer.customize(context);
 
 		JoseHeader headers = context.getHeaders().build();

+ 20 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java

@@ -21,6 +21,7 @@ import java.time.Instant;
 import java.util.Base64;
 import java.util.Set;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -33,6 +34,7 @@ import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
+import org.springframework.security.oauth2.core.OAuth2TokenType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.jwt.JoseHeader;
 import org.springframework.security.oauth2.jwt.Jwt;
@@ -40,8 +42,8 @@ import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
-import org.springframework.security.oauth2.core.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
 import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@@ -69,6 +71,7 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
 	private final OAuth2AuthorizationService authorizationService;
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
+	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters.
@@ -89,6 +92,11 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
 		this.jwtCustomizer = jwtCustomizer;
 	}
 
+	@Autowired(required = false)
+	protected void setProviderSettings(ProviderSettings providerSettings) {
+		this.providerSettings = providerSettings;
+	}
+
 	@Override
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication =
@@ -137,13 +145,23 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
 		}
 
+		String issuer = this.providerSettings != null ? this.providerSettings.issuer() : null;
+
+		JoseHeader.Builder headersBuilder = JwtUtils.headers();
+		JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(
+				registeredClient, issuer, authorization.getPrincipalName(), scopes);
+
 		// @formatter:off
-		JwtEncodingContext context = JwtEncodingContextUtils.accessTokenContext(registeredClient, authorization, scopes)
+		JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
+				.registeredClient(registeredClient)
 				.principal(authorization.getAttribute(Principal.class.getName()))
+				.authorization(authorization)
+				.tokenType(OAuth2TokenType.ACCESS_TOKEN)
 				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
 				.authorizationGrant(refreshTokenAuthentication)
 				.build();
 		// @formatter:on
+
 		this.jwtCustomizer.customize(context);
 
 		JoseHeader headers = context.getHeaders().build();