From 1db0d4f83dd4c4cf0e905a70dc2b998d3f4d37d3 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:56:17 -0400 Subject: [PATCH] Enable null-safety in spring-security-oauth2-authorization-server Closes gh-18937 --- ...ecurity-oauth2-authorization-server.gradle | 1 + ...moryOAuth2AuthorizationConsentService.java | 6 +- .../InMemoryOAuth2AuthorizationService.java | 9 +- ...JdbcOAuth2AuthorizationConsentService.java | 8 +- .../JdbcOAuth2AuthorizationService.java | 85 +++++++++++-------- .../authorization/OAuth2Authorization.java | 64 ++++++++------ .../OAuth2AuthorizationConsent.java | 8 +- .../OAuth2AuthorizationConsentService.java | 6 +- ...horizationServerMetadataClaimAccessor.java | 46 ++++++---- .../OAuth2AuthorizationService.java | 8 +- .../OAuth2ClientMetadataClaimAccessor.java | 27 +++--- ...ionServerBeanRegistrationAotProcessor.java | 4 +- ...OAuth2AuthorizationServerRuntimeHints.java | 4 +- .../authorization/aot/hint/package-info.java | 24 ++++++ ...izationCodeRequestAuthenticationToken.java | 13 ++- .../ClientSecretAuthenticationProvider.java | 22 ++--- .../CodeVerifierAuthenticator.java | 25 +++--- .../authentication/DPoPProofVerifier.java | 8 +- ...ClientAssertionAuthenticationProvider.java | 25 +++--- ...Auth2AccessTokenAuthenticationContext.java | 10 ++- .../OAuth2AccessTokenAuthenticationToken.java | 8 +- .../OAuth2AuthenticationContext.java | 8 +- .../OAuth2AuthenticationProviderUtils.java | 5 +- ...thorizationCodeAuthenticationProvider.java | 15 +++- ...2AuthorizationCodeAuthenticationToken.java | 8 +- .../OAuth2AuthorizationCodeGenerator.java | 8 +- ...ationCodeRequestAuthenticationContext.java | 16 ++-- ...ionCodeRequestAuthenticationException.java | 8 +- ...tionCodeRequestAuthenticationProvider.java | 61 +++++++------ ...izationCodeRequestAuthenticationToken.java | 8 +- ...ionCodeRequestAuthenticationValidator.java | 41 +++++---- ...orizationConsentAuthenticationContext.java | 20 +++-- ...rizationConsentAuthenticationProvider.java | 51 ++++++----- ...thorizationConsentAuthenticationToken.java | 3 +- ...AuthorizationGrantAuthenticationToken.java | 3 +- .../OAuth2ClientAuthenticationContext.java | 10 ++- .../OAuth2ClientAuthenticationToken.java | 13 ++- ...lientCredentialsAuthenticationContext.java | 10 ++- ...ientCredentialsAuthenticationProvider.java | 1 + ...2ClientCredentialsAuthenticationToken.java | 3 +- ...entRegistrationAuthenticationProvider.java | 17 ++-- ...ClientRegistrationAuthenticationToken.java | 9 +- ...rizationConsentAuthenticationProvider.java | 22 +++-- ...thorizationConsentAuthenticationToken.java | 9 +- ...rizationRequestAuthenticationProvider.java | 9 +- ...thorizationRequestAuthenticationToken.java | 15 ++-- ...Auth2DeviceCodeAuthenticationProvider.java | 11 ++- .../OAuth2DeviceCodeAuthenticationToken.java | 3 +- ...viceVerificationAuthenticationContext.java | 17 ++-- ...iceVerificationAuthenticationProvider.java | 8 +- ...DeviceVerificationAuthenticationToken.java | 9 +- ...rizationRequestAuthenticationProvider.java | 1 + ...thorizationRequestAuthenticationToken.java | 13 ++- .../OAuth2PushedAuthorizationRequestUri.java | 29 +++---- ...th2RefreshTokenAuthenticationProvider.java | 11 ++- ...OAuth2RefreshTokenAuthenticationToken.java | 3 +- .../OAuth2TokenExchangeActor.java | 8 +- ...h2TokenExchangeAuthenticationProvider.java | 23 +++-- ...Auth2TokenExchangeAuthenticationToken.java | 15 ++-- ...nExchangeCompositeAuthenticationToken.java | 6 +- ...enIntrospectionAuthenticationProvider.java | 2 + ...TokenIntrospectionAuthenticationToken.java | 8 +- ...TokenRevocationAuthenticationProvider.java | 2 + ...th2TokenRevocationAuthenticationToken.java | 8 +- .../PublicClientAuthenticationProvider.java | 11 +-- ...ientCertificateAuthenticationProvider.java | 20 +++-- .../X509SelfSignedCertificateVerifier.java | 42 +++++---- .../authentication/package-info.java | 25 ++++++ .../InMemoryRegisteredClientRepository.java | 9 +- .../JdbcRegisteredClientRepository.java | 22 +++-- .../client/RegisteredClient.java | 64 ++++++++------ .../client/RegisteredClientRepository.java | 8 +- .../authorization/client/package-info.java | 25 ++++++ .../server/authorization/context/Context.java | 9 +- .../authorization/context/package-info.java | 24 ++++++ ...RegistrationRegisteredClientConverter.java | 14 +-- ...ientOAuth2ClientRegistrationConverter.java | 7 +- .../authorization/converter/package-info.java | 24 ++++++ .../http/converter/HttpMessageConverters.java | 4 +- ...ionServerMetadataHttpMessageConverter.java | 6 +- ...lientRegistrationHttpMessageConverter.java | 10 ++- ...okenIntrospectionHttpMessageConverter.java | 6 +- .../http/converter/package-info.java | 23 +++++ .../authorization/jackson/JsonNodeUtils.java | 7 +- ...Auth2AuthorizationRequestDeserializer.java | 33 +++++-- .../authorization/jackson/package-info.java | 23 +++++ .../authorization/jackson2/JsonNodeUtils.java | 7 +- ...Auth2AuthorizationRequestDeserializer.java | 36 ++++++-- .../authorization/jackson2/package-info.java | 24 ++++++ .../oidc/OidcClientMetadataClaimAccessor.java | 12 +-- .../OidcProviderMetadataClaimAccessor.java | 18 +++- ...ntConfigurationAuthenticationProvider.java | 16 ++-- ...entRegistrationAuthenticationProvider.java | 39 ++++++--- ...ClientRegistrationAuthenticationToken.java | 12 +-- .../OidcLogoutAuthenticationContext.java | 10 ++- .../OidcLogoutAuthenticationProvider.java | 26 +++--- .../OidcLogoutAuthenticationToken.java | 28 +++--- .../OidcUserInfoAuthenticationContext.java | 14 +-- .../OidcUserInfoAuthenticationProvider.java | 5 +- .../OidcUserInfoAuthenticationToken.java | 8 +- .../oidc/authentication/package-info.java | 25 ++++++ ...RegistrationRegisteredClientConverter.java | 26 ++++-- ...ClientOidcClientRegistrationConverter.java | 7 +- .../oidc/converter/package-info.java | 24 ++++++ .../http/converter/HttpMessageConverters.java | 4 +- ...lientRegistrationHttpMessageConverter.java | 10 ++- ...iderConfigurationHttpMessageConverter.java | 6 +- .../OidcUserInfoHttpMessageConverter.java | 6 +- .../oidc/http/converter/package-info.java | 23 +++++ .../authorization/oidc/package-info.java | 24 ++++++ .../OidcClientRegistrationEndpointFilter.java | 2 + .../oidc/web/OidcLogoutEndpointFilter.java | 1 + .../oidc/web/OidcUserInfoEndpointFilter.java | 6 +- ...ntRegistrationAuthenticationConverter.java | 7 +- .../OidcLogoutAuthenticationConverter.java | 27 +++--- ...idcLogoutAuthenticationSuccessHandler.java | 7 +- .../oidc/web/authentication/package-info.java | 24 ++++++ .../authorization/oidc/web/package-info.java | 23 +++++ .../server/authorization/package-info.java | 24 ++++++ .../settings/AbstractSettings.java | 4 +- .../settings/AuthorizationServerSettings.java | 64 ++++++++++---- .../settings/ClientSettings.java | 21 +++-- .../authorization/settings/TokenSettings.java | 30 +++++-- .../authorization/settings/package-info.java | 23 +++++ .../token/DefaultOAuth2TokenContext.java | 6 +- .../token/DelegatingOAuth2TokenGenerator.java | 6 +- .../token/JwtEncodingContext.java | 14 +-- .../authorization/token/JwtGenerator.java | 69 ++++++++------- .../token/OAuth2AccessTokenGenerator.java | 39 ++++----- .../token/OAuth2RefreshTokenGenerator.java | 13 +-- .../token/OAuth2TokenClaimAccessor.java | 32 ++++--- .../token/OAuth2TokenClaimsContext.java | 10 ++- .../token/OAuth2TokenContext.java | 37 ++++---- .../token/OAuth2TokenGenerator.java | 6 +- .../authorization/token/package-info.java | 24 ++++++ .../web/HttpMessageConverters.java | 4 +- .../OAuth2AuthorizationEndpointFilter.java | 24 ++++-- ...Auth2ClientRegistrationEndpointFilter.java | 1 + ...uth2DeviceAuthorizationEndpointFilter.java | 4 + ...Auth2DeviceVerificationEndpointFilter.java | 9 +- ...hedAuthorizationRequestEndpointFilter.java | 11 ++- .../web/OAuth2TokenEndpointFilter.java | 8 +- ...Auth2TokenIntrospectionEndpointFilter.java | 1 + .../OAuth2TokenRevocationEndpointFilter.java | 1 + ...entSecretBasicAuthenticationConverter.java | 5 +- ...ientSecretPostAuthenticationConverter.java | 12 +-- ...lientAssertionAuthenticationConverter.java | 15 ++-- ...nResponseAuthenticationSuccessHandler.java | 3 +- ...horizationCodeAuthenticationConverter.java | 19 +++-- ...ionCodeRequestAuthenticationConverter.java | 41 ++++++--- ...izationConsentAuthenticationConverter.java | 13 ++- ...entCredentialsAuthenticationConverter.java | 15 ++-- ...izationConsentAuthenticationConverter.java | 19 +++-- ...izationRequestAuthenticationConverter.java | 13 ++- ...uth2DeviceCodeAuthenticationConverter.java | 16 ++-- ...ceVerificationAuthenticationConverter.java | 11 ++- ...h2RefreshTokenAuthenticationConverter.java | 19 +++-- ...2TokenExchangeAuthenticationConverter.java | 35 +++++--- ...nIntrospectionAuthenticationConverter.java | 17 ++-- ...okenRevocationAuthenticationConverter.java | 18 ++-- .../PublicClientAuthenticationConverter.java | 12 +-- ...entCertificateAuthenticationConverter.java | 9 +- .../web/authentication/package-info.java | 24 ++++++ .../authorization/web/package-info.java | 23 +++++ .../TestAuthorizationServerContext.java | 3 +- ...ClientRegistrationEndpointFilterTests.java | 8 ++ 166 files changed, 1861 insertions(+), 858 deletions(-) create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/package-info.java create mode 100644 oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/package-info.java diff --git a/oauth2/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle b/oauth2/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle index af669f35b7..f5fdc17428 100644 --- a/oauth2/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle +++ b/oauth2/oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle @@ -1,5 +1,6 @@ plugins { id 'compile-warnings-error' + id 'security-nullability' } apply plugin: 'io.spring.convention.spring-module' diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java index a82c81d8e4..247668bab8 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationConsentService.java @@ -23,7 +23,8 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -90,8 +91,7 @@ public final class InMemoryOAuth2AuthorizationConsentService implements OAuth2Au } @Override - @Nullable - public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { + public @Nullable OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); Assert.hasText(principalName, "principalName cannot be empty"); int id = getId(registeredClientId, principalName); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java index a4fb51e2d7..da1b94efa0 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/InMemoryOAuth2AuthorizationService.java @@ -23,7 +23,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2DeviceCode; import org.springframework.security.oauth2.core.OAuth2RefreshToken; @@ -125,17 +126,15 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza } } - @Nullable @Override - public OAuth2Authorization findById(String id) { + public @Nullable OAuth2Authorization findById(String id) { Assert.hasText(id, "id cannot be empty"); OAuth2Authorization authorization = this.authorizations.get(id); return (authorization != null) ? authorization : this.initializedAuthorizations.get(id); } - @Nullable @Override - public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { + public @Nullable OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { Assert.hasText(token, "token cannot be empty"); for (OAuth2Authorization authorization : this.authorizations.values()) { if (hasToken(authorization, token, tokenType)) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java index ee3456ea5d..ecd8eefc87 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.context.annotation.ImportRuntimeHints; @@ -35,7 +37,6 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.lang.Nullable; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; @@ -162,8 +163,7 @@ public class JdbcOAuth2AuthorizationConsentService implements OAuth2Authorizatio } @Override - @Nullable - public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { + public @Nullable OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); Assert.hasText(principalName, "principalName cannot be empty"); SqlParameterValue[] parameters = new SqlParameterValue[] { @@ -281,7 +281,7 @@ public class JdbcOAuth2AuthorizationConsentService implements OAuth2Authorizatio static class JdbcOAuth2AuthorizationConsentServiceRuntimeHintsRegistrar implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources() .registerResource(new ClassPathResource( "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java index 852f958f55..a86421e283 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization; import java.nio.charset.StandardCharsets; +import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -36,6 +37,7 @@ import java.util.function.Function; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jspecify.annotations.Nullable; import tools.jackson.databind.JacksonModule; import tools.jackson.databind.json.JsonMapper; @@ -54,7 +56,6 @@ import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.jdbc.support.lob.LobCreator; import org.springframework.jdbc.support.lob.LobHandler; -import org.springframework.lang.Nullable; import org.springframework.security.jackson.SecurityJacksonModules; import org.springframework.security.jackson2.SecurityJackson2Modules; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -210,7 +211,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; - private static Map columnMetadataMap; + private static final Map columnMetadataMap = new HashMap<>(); private final JdbcOperations jdbcOperations; @@ -292,18 +293,16 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic this.jdbcOperations.update(REMOVE_AUTHORIZATION_SQL, pss); } - @Nullable @Override - public OAuth2Authorization findById(String id) { + public @Nullable OAuth2Authorization findById(String id) { Assert.hasText(id, "id cannot be empty"); List parameters = new ArrayList<>(); parameters.add(new SqlParameterValue(Types.VARCHAR, id)); return findBy(PK_FILTER, parameters); } - @Nullable @Override - public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { + public @Nullable OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { Assert.hasText(token, "token cannot be empty"); List parameters = new ArrayList<>(); if (tokenType == null) { @@ -347,7 +346,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic return null; } - private OAuth2Authorization findBy(String filter, List parameters) { + private @Nullable OAuth2Authorization findBy(String filter, List parameters) { try (LobCreator lobCreator = getLobHandler().getLobCreator()) { PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, parameters.toArray()); @@ -399,7 +398,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } private static void initColumnMetadata(JdbcOperations jdbcOperations) { - columnMetadataMap = new HashMap<>(); + columnMetadataMap.clear(); ColumnMetadata columnMetadata; columnMetadata = getColumnMetadata(jdbcOperations, "attributes", Types.BLOB); @@ -432,32 +431,37 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic private static ColumnMetadata getColumnMetadata(JdbcOperations jdbcOperations, String columnName, int defaultDataType) { - Integer dataType = jdbcOperations.execute((ConnectionCallback) (conn) -> { - DatabaseMetaData databaseMetaData = conn.getMetaData(); - ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName); - if (rs.next()) { - return rs.getInt("DATA_TYPE"); + @Nullable Integer dataType = jdbcOperations.execute(new ConnectionCallback<@Nullable Integer>() { + @Override + public @Nullable Integer doInConnection(Connection conn) throws SQLException { + DatabaseMetaData databaseMetaData = conn.getMetaData(); + ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName); + if (rs.next()) { + return rs.getInt("DATA_TYPE"); + } + // NOTE: (Applies to HSQL) + // When a database object is created with one of the CREATE statements or + // renamed with the ALTER statement, + // if the name is enclosed in double quotes, the exact name is used as the + // case-normal form. + // But if it is not enclosed in double quotes, + // the name is converted to uppercase and this uppercase version is stored + // in + // the database as the case-normal form. + rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(Locale.ENGLISH), + columnName.toUpperCase(Locale.ENGLISH)); + if (rs.next()) { + return rs.getInt("DATA_TYPE"); + } + return null; } - // NOTE: (Applies to HSQL) - // When a database object is created with one of the CREATE statements or - // renamed with the ALTER statement, - // if the name is enclosed in double quotes, the exact name is used as the - // case-normal form. - // But if it is not enclosed in double quotes, - // the name is converted to uppercase and this uppercase version is stored in - // the database as the case-normal form. - rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(Locale.ENGLISH), - columnName.toUpperCase(Locale.ENGLISH)); - if (rs.next()) { - return rs.getInt("DATA_TYPE"); - } - return null; }); return new ColumnMetadata(columnName, (dataType != null) ? dataType : defaultDataType); } - private static SqlParameterValue mapToSqlParameter(String columnName, String value) { + private static SqlParameterValue mapToSqlParameter(String columnName, @Nullable String value) { ColumnMetadata columnMetadata = columnMetadataMap.get(columnName); + Assert.notNull(columnMetadata, "Column metadata not found for column '" + columnName + "'"); return (Types.BLOB == columnMetadata.getDataType() && StringUtils.hasText(value)) ? new SqlParameterValue(Types.BLOB, value.getBytes(StandardCharsets.UTF_8)) : new SqlParameterValue(columnMetadata.getDataType(), value); @@ -610,6 +614,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic .equalsIgnoreCase(rs.getString("access_token_type"))) { tokenType = OAuth2AccessToken.TokenType.DPOP; } + Assert.notNull(tokenType, "access_token_type must be BEARER or DPOP"); Set scopes = Collections.emptySet(); String accessTokenScopes = rs.getString("access_token_scopes"); @@ -627,8 +632,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic tokenExpiresAt = rs.getTimestamp("oidc_id_token_expires_at").toInstant(); Map oidcTokenMetadata = parseMap(getLobValue(rs, OIDC_ID_TOKEN_METADATA)); - OidcIdToken oidcToken = new OidcIdToken(oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt, - (Map) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME)); + @SuppressWarnings("unchecked") + Map idTokenClaims = (Map) oidcTokenMetadata + .get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME); + if (idTokenClaims == null) { + idTokenClaims = Collections.emptyMap(); + } + OidcIdToken oidcToken = new OidcIdToken(oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt, idTokenClaims); builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata)); } @@ -670,9 +680,10 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic return builder.build(); } - private String getLobValue(ResultSet rs, String columnName) throws SQLException { + private @Nullable String getLobValue(ResultSet rs, String columnName) throws SQLException { String columnValue = null; ColumnMetadata columnMetadata = columnMetadataMap.get(columnName); + Assert.notNull(columnMetadata, "Column metadata not found for column '" + columnName + "'"); if (Types.BLOB == columnMetadata.getDataType()) { byte[] columnValueBytes = this.lobHandler.getBlobAsBytes(rs, columnName); if (columnValueBytes != null) { @@ -701,7 +712,10 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic return this.lobHandler; } - private Map parseMap(String data) { + private Map parseMap(@Nullable String data) { + if (!StringUtils.hasText(data)) { + return Collections.emptyMap(); + } try { return readValue(data); } @@ -849,7 +863,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } private List toSqlParameterList(String tokenColumnName, - String tokenMetadataColumnName, OAuth2Authorization.Token token) { + String tokenMetadataColumnName, OAuth2Authorization.@Nullable Token token) { List parameters = new ArrayList<>(); String tokenValue = null; @@ -933,7 +947,8 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } @Override - protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException { + protected void doSetValue(PreparedStatement ps, int parameterPosition, @Nullable Object argValue) + throws SQLException { if (argValue instanceof SqlParameterValue paramValue) { if (paramValue.getSqlType() == Types.BLOB) { if (paramValue.getValue() != null) { @@ -983,7 +998,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic static class JdbcOAuth2AuthorizationServiceRuntimeHintsRegistrar implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources() .registerResource(new ClassPathResource( "org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java index b62f4c4a83..035c57a8fb 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java @@ -28,7 +28,8 @@ import java.util.Set; import java.util.UUID; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; @@ -58,19 +59,19 @@ public class OAuth2Authorization implements Serializable { @Serial private static final long serialVersionUID = 880363144799377926L; - private String id; + private @Nullable String id; - private String registeredClientId; + private @Nullable String registeredClientId; - private String principalName; + private @Nullable String principalName; - private AuthorizationGrantType authorizationGrantType; + private @Nullable AuthorizationGrantType authorizationGrantType; - private Set authorizedScopes; + private @Nullable Set authorizedScopes; - private Map, Token> tokens; + private @Nullable Map, Token> tokens; - private Map attributes; + private @Nullable Map attributes; protected OAuth2Authorization() { } @@ -80,6 +81,7 @@ public class OAuth2Authorization implements Serializable { * @return the identifier for the authorization */ public String getId() { + Assert.notNull(this.id, "id cannot be null"); return this.id; } @@ -88,6 +90,7 @@ public class OAuth2Authorization implements Serializable { * @return the {@link RegisteredClient#getId()} */ public String getRegisteredClientId() { + Assert.notNull(this.registeredClientId, "registeredClientId cannot be null"); return this.registeredClientId; } @@ -96,6 +99,7 @@ public class OAuth2Authorization implements Serializable { * @return the {@code Principal} name of the resource owner (or client) */ public String getPrincipalName() { + Assert.notNull(this.principalName, "principalName cannot be null"); return this.principalName; } @@ -105,6 +109,7 @@ public class OAuth2Authorization implements Serializable { * @return the {@link AuthorizationGrantType} used for the authorization */ public AuthorizationGrantType getAuthorizationGrantType() { + Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null"); return this.authorizationGrantType; } @@ -113,14 +118,16 @@ public class OAuth2Authorization implements Serializable { * @return the {@code Set} of authorized scope(s) */ public Set getAuthorizedScopes() { + Assert.notNull(this.authorizedScopes, "authorizedScopes cannot be null"); return this.authorizedScopes; } /** * Returns the {@link Token} of type {@link OAuth2AccessToken}. - * @return the {@link Token} of type {@link OAuth2AccessToken} + * @return the {@link Token} of type {@link OAuth2AccessToken}, or {@code null} if not + * available */ - public Token getAccessToken() { + public @Nullable Token getAccessToken() { return getToken(OAuth2AccessToken.class); } @@ -129,8 +136,7 @@ public class OAuth2Authorization implements Serializable { * @return the {@link Token} of type {@link OAuth2RefreshToken}, or {@code null} if * not available */ - @Nullable - public Token getRefreshToken() { + public @Nullable Token getRefreshToken() { return getToken(OAuth2RefreshToken.class); } @@ -140,10 +146,10 @@ public class OAuth2Authorization implements Serializable { * @param the type of the token * @return the {@link Token}, or {@code null} if not available */ - @Nullable @SuppressWarnings("unchecked") - public Token getToken(Class tokenType) { + public @Nullable Token getToken(Class tokenType) { Assert.notNull(tokenType, "tokenType cannot be null"); + Assert.notNull(this.tokens, "tokens cannot be null"); Token token = this.tokens.get(tokenType); return (token != null) ? (Token) token : null; } @@ -154,10 +160,10 @@ public class OAuth2Authorization implements Serializable { * @param the type of the token * @return the {@link Token}, or {@code null} if not available */ - @Nullable @SuppressWarnings("unchecked") - public Token getToken(String tokenValue) { + public @Nullable Token getToken(String tokenValue) { Assert.hasText(tokenValue, "tokenValue cannot be empty"); + Assert.notNull(this.tokens, "tokens cannot be null"); for (Token token : this.tokens.values()) { if (token.getToken().getTokenValue().equals(tokenValue)) { return (Token) token; @@ -171,6 +177,7 @@ public class OAuth2Authorization implements Serializable { * @return a {@code Map} of the attribute(s) */ public Map getAttributes() { + Assert.notNull(this.attributes, "attributes cannot be null"); return this.attributes; } @@ -181,10 +188,10 @@ public class OAuth2Authorization implements Serializable { * @return the value of an attribute associated to the authorization, or {@code null} * if not available */ - @Nullable @SuppressWarnings("unchecked") - public T getAttribute(String name) { + public @Nullable T getAttribute(String name) { Assert.hasText(name, "name cannot be empty"); + Assert.notNull(this.attributes, "attributes cannot be null"); return (T) this.attributes.get(name); } @@ -230,6 +237,7 @@ public class OAuth2Authorization implements Serializable { */ public static Builder from(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); + Assert.notNull(authorization.tokens, "tokens cannot be null"); return new Builder(authorization.getRegisteredClientId()).id(authorization.getId()) .principalName(authorization.getPrincipalName()) .authorizationGrantType(authorization.getAuthorizationGrantType()) @@ -324,8 +332,7 @@ public class OAuth2Authorization implements Serializable { * Returns the claims associated to the token. * @return a {@code Map} of the claims, or {@code null} if not available */ - @Nullable - public Map getClaims() { + public @Nullable Map getClaims() { return getMetadata(CLAIMS_METADATA_NAME); } @@ -335,9 +342,8 @@ public class OAuth2Authorization implements Serializable { * @param the value type of the metadata * @return the value of the metadata, or {@code null} if not available */ - @Nullable @SuppressWarnings("unchecked") - public V getMetadata(String name) { + public @Nullable V getMetadata(String name) { Assert.hasText(name, "name cannot be empty"); return (V) this.metadata.get(name); } @@ -380,15 +386,15 @@ public class OAuth2Authorization implements Serializable { */ public static class Builder { - private String id; + private @Nullable String id; private final String registeredClientId; - private String principalName; + private @Nullable String principalName; - private AuthorizationGrantType authorizationGrantType; + private @Nullable AuthorizationGrantType authorizationGrantType; - private Set authorizedScopes; + private @Nullable Set authorizedScopes; private Map, Token> tokens = new HashMap<>(); @@ -503,8 +509,10 @@ public class OAuth2Authorization implements Serializable { token(token, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); if (OAuth2RefreshToken.class.isAssignableFrom(token.getClass())) { Token accessToken = this.tokens.get(OAuth2AccessToken.class); - token(accessToken.getToken(), - (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); + if (accessToken != null) { + token(accessToken.getToken(), + (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true)); + } Token authorizationCode = this.tokens.get(OAuth2AuthorizationCode.class); if (authorizationCode != null && !authorizationCode.isInvalidated()) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java index 4db6dd72b2..19151f7825 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java @@ -24,7 +24,6 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; -import org.springframework.lang.NonNull; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; @@ -99,8 +98,9 @@ public final class OAuth2AuthorizationConsent implements Serializable { public Set getScopes() { Set authorities = new HashSet<>(); for (GrantedAuthority authority : getAuthorities()) { - if (authority.getAuthority().startsWith(AUTHORITIES_SCOPE_PREFIX)) { - authorities.add(authority.getAuthority().substring(AUTHORITIES_SCOPE_PREFIX.length())); + String authorityValue = authority.getAuthority(); + if (authorityValue != null && authorityValue.startsWith(AUTHORITIES_SCOPE_PREFIX)) { + authorities.add(authorityValue.substring(AUTHORITIES_SCOPE_PREFIX.length())); } } return authorities; @@ -146,7 +146,7 @@ public final class OAuth2AuthorizationConsent implements Serializable { * @param principalName the {@code Principal} name * @return the {@link Builder} */ - public static Builder withId(@NonNull String registeredClientId, @NonNull String principalName) { + public static Builder withId(String registeredClientId, String principalName) { Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); Assert.hasText(principalName, "principalName cannot be empty"); return new Builder(registeredClientId, principalName); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java index 20b16d9347..aeadbcf795 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsentService.java @@ -18,7 +18,8 @@ package org.springframework.security.oauth2.server.authorization; import java.security.Principal; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; /** @@ -50,7 +51,6 @@ public interface OAuth2AuthorizationConsentService { * @param principalName the name of the {@link Principal} * @return the {@link OAuth2AuthorizationConsent} if found, otherwise {@code null} */ - @Nullable - OAuth2AuthorizationConsent findById(String registeredClientId, String principalName); + @Nullable OAuth2AuthorizationConsent findById(String registeredClientId, String principalName); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java index a5432d4e27..11938cdae8 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java @@ -19,8 +19,11 @@ package org.springframework.security.oauth2.server.authorization; import java.net.URL; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.util.Assert; /** * A {@link ClaimAccessor} for the "claims" an Authorization Server describes about its @@ -57,7 +60,9 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the {@code URL} the Authorization Server asserts as its Issuer Identifier */ default URL getIssuer() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.ISSUER); + URL issuer = getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.ISSUER); + Assert.notNull(issuer, "issuer cannot be null"); + return issuer; } /** @@ -66,7 +71,9 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the {@code URL} of the OAuth 2.0 Authorization Endpoint */ default URL getAuthorizationEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT); + URL authorizationEndpoint = getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT); + Assert.notNull(authorizationEndpoint, "authorizationEndpoint cannot be null"); + return authorizationEndpoint; } /** @@ -74,7 +81,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (pushed_authorization_request_endpoint)}. * @return the {@code URL} of the OAuth 2.0 Pushed Authorization Request Endpoint */ - default URL getPushedAuthorizationRequestEndpoint() { + default @Nullable URL getPushedAuthorizationRequestEndpoint() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT); } @@ -83,7 +90,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (device_authorization_endpoint)}. * @return the {@code URL} of the OAuth 2.0 Device Authorization Endpoint */ - default URL getDeviceAuthorizationEndpoint() { + default @Nullable URL getDeviceAuthorizationEndpoint() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT); } @@ -92,7 +99,9 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the {@code URL} of the OAuth 2.0 Token Endpoint */ default URL getTokenEndpoint() { - return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT); + URL tokenEndpoint = getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT); + Assert.notNull(tokenEndpoint, "tokenEndpoint cannot be null"); + return tokenEndpoint; } /** @@ -100,7 +109,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (token_endpoint_auth_methods_supported)}. * @return the client authentication methods supported by the OAuth 2.0 Token Endpoint */ - default List getTokenEndpointAuthenticationMethods() { + default @Nullable List getTokenEndpointAuthenticationMethods() { return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED); } @@ -108,7 +117,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * Returns the {@code URL} of the JSON Web Key Set {@code (jwks_uri)}. * @return the {@code URL} of the JSON Web Key Set */ - default URL getJwkSetUrl() { + default @Nullable URL getJwkSetUrl() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI); } @@ -116,7 +125,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}. * @return the OAuth 2.0 {@code scope} values supported */ - default List getScopes() { + default @Nullable List getScopes() { return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED); } @@ -126,7 +135,10 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the OAuth 2.0 {@code response_type} values supported */ default List getResponseTypes() { - return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED); + List responseTypes = getClaimAsStringList( + OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED); + Assert.notNull(responseTypes, "responseTypes cannot be null"); + return responseTypes; } /** @@ -134,7 +146,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (grant_types_supported)}. * @return the OAuth 2.0 {@code grant_type} values supported */ - default List getGrantTypes() { + default @Nullable List getGrantTypes() { return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED); } @@ -143,7 +155,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (revocation_endpoint)}. * @return the {@code URL} of the OAuth 2.0 Token Revocation Endpoint */ - default URL getTokenRevocationEndpoint() { + default @Nullable URL getTokenRevocationEndpoint() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT); } @@ -153,7 +165,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the client authentication methods supported by the OAuth 2.0 Token * Revocation Endpoint */ - default List getTokenRevocationEndpointAuthenticationMethods() { + default @Nullable List getTokenRevocationEndpointAuthenticationMethods() { return getClaimAsStringList( OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED); } @@ -163,7 +175,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (introspection_endpoint)}. * @return the {@code URL} of the OAuth 2.0 Token Introspection Endpoint */ - default URL getTokenIntrospectionEndpoint() { + default @Nullable URL getTokenIntrospectionEndpoint() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT); } @@ -173,7 +185,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the client authentication methods supported by the OAuth 2.0 Token * Introspection Endpoint */ - default List getTokenIntrospectionEndpointAuthenticationMethods() { + default @Nullable List getTokenIntrospectionEndpointAuthenticationMethods() { return getClaimAsStringList( OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED); } @@ -183,7 +195,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * {@code (registration_endpoint)}. * @return the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint */ - default URL getClientRegistrationEndpoint() { + default @Nullable URL getClientRegistrationEndpoint() { return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT); } @@ -192,7 +204,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * supported {@code (code_challenge_methods_supported)}. * @return the {@code code_challenge_method} values supported */ - default List getCodeChallengeMethods() { + default @Nullable List getCodeChallengeMethods() { return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED); } @@ -213,7 +225,7 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc * @return the {@link JwsAlgorithms JSON Web Signature (JWS) algorithms} supported for * DPoP Proof JWTs */ - default List getDPoPSigningAlgorithms() { + default @Nullable List getDPoPSigningAlgorithms() { return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.DPOP_SIGNING_ALG_VALUES_SUPPORTED); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java index a4ebd9205e..27fab45d21 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationService.java @@ -16,7 +16,7 @@ package org.springframework.security.oauth2.server.authorization; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Implementations of this interface are responsible for the management of @@ -47,8 +47,7 @@ public interface OAuth2AuthorizationService { * @param id the authorization identifier * @return the {@link OAuth2Authorization} if found, otherwise {@code null} */ - @Nullable - OAuth2Authorization findById(String id); + @Nullable OAuth2Authorization findById(String id); /** * Returns the {@link OAuth2Authorization} containing the provided {@code token}, or @@ -57,7 +56,6 @@ public interface OAuth2AuthorizationService { * @param tokenType the {@link OAuth2TokenType token type} * @return the {@link OAuth2Authorization} if found, otherwise {@code null} */ - @Nullable - OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType); + @Nullable OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2ClientMetadataClaimAccessor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2ClientMetadataClaimAccessor.java index 655ca7b7c6..d04c360e66 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2ClientMetadataClaimAccessor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2ClientMetadataClaimAccessor.java @@ -20,7 +20,10 @@ import java.net.URL; import java.time.Instant; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClaimAccessor; +import org.springframework.util.Assert; /** * A {@link ClaimAccessor} for the claims that are contained in the OAuth 2.0 Client @@ -41,7 +44,9 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * @return the Client Identifier */ default String getClientId() { - return getClaimAsString(OAuth2ClientMetadataClaimNames.CLIENT_ID); + String clientId = getClaimAsString(OAuth2ClientMetadataClaimNames.CLIENT_ID); + Assert.notNull(clientId, "clientId cannot be null"); + return clientId; } /** @@ -49,7 +54,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * {@code (client_id_issued_at)}. * @return the time at which the Client Identifier was issued */ - default Instant getClientIdIssuedAt() { + default @Nullable Instant getClientIdIssuedAt() { return getClaimAsInstant(OAuth2ClientMetadataClaimNames.CLIENT_ID_ISSUED_AT); } @@ -57,7 +62,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * Returns the Client Secret {@code (client_secret)}. * @return the Client Secret */ - default String getClientSecret() { + default @Nullable String getClientSecret() { return getClaimAsString(OAuth2ClientMetadataClaimNames.CLIENT_SECRET); } @@ -66,7 +71,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * {@code (client_secret_expires_at)}. * @return the time at which the {@code client_secret} will expire */ - default Instant getClientSecretExpiresAt() { + default @Nullable Instant getClientSecretExpiresAt() { return getClaimAsInstant(OAuth2ClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT); } @@ -75,7 +80,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * {@code (client_name)}. * @return the name of the Client to be presented to the End-User */ - default String getClientName() { + default @Nullable String getClientName() { return getClaimAsString(OAuth2ClientMetadataClaimNames.CLIENT_NAME); } @@ -84,7 +89,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * {@code (redirect_uris)}. * @return the redirection {@code URI} values used by the Client */ - default List getRedirectUris() { + default @Nullable List getRedirectUris() { return getClaimAsStringList(OAuth2ClientMetadataClaimNames.REDIRECT_URIS); } @@ -93,7 +98,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * {@code (token_endpoint_auth_method)}. * @return the authentication method used by the Client for the Token Endpoint */ - default String getTokenEndpointAuthenticationMethod() { + default @Nullable String getTokenEndpointAuthenticationMethod() { return getClaimAsString(OAuth2ClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); } @@ -103,7 +108,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * @return the OAuth 2.0 {@code grant_type} values that the Client will restrict * itself to using */ - default List getGrantTypes() { + default @Nullable List getGrantTypes() { return getClaimAsStringList(OAuth2ClientMetadataClaimNames.GRANT_TYPES); } @@ -113,7 +118,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * @return the OAuth 2.0 {@code response_type} values that the Client will restrict * itself to using */ - default List getResponseTypes() { + default @Nullable List getResponseTypes() { return getClaimAsStringList(OAuth2ClientMetadataClaimNames.RESPONSE_TYPES); } @@ -123,7 +128,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * @return the OAuth 2.0 {@code scope} values that the Client will restrict itself to * using */ - default List getScopes() { + default @Nullable List getScopes() { return getClaimAsStringList(OAuth2ClientMetadataClaimNames.SCOPE); } @@ -131,7 +136,7 @@ public interface OAuth2ClientMetadataClaimAccessor extends ClaimAccessor { * Returns the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)}. * @return the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)} */ - default URL getJwkSetUrl() { + default @Nullable URL getJwkSetUrl() { return getClaimAsURL(OAuth2ClientMetadataClaimNames.JWKS_URI); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java index 34199fdaf4..7f7a4fbe56 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java @@ -20,6 +20,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.BindingReflectionHintsRegistrar; import org.springframework.aot.hint.MemberCategory; @@ -84,7 +86,7 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis private boolean jacksonContributed; @Override - public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { boolean isJdbcBasedOAuth2AuthorizationService = JdbcOAuth2AuthorizationService.class .isAssignableFrom(registeredBean.getBeanClass()); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerRuntimeHints.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerRuntimeHints.java index a9227faafc..0fc66310b1 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerRuntimeHints.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerRuntimeHints.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.aot.hint; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -35,7 +37,7 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2Author class OAuth2AuthorizationServerRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection() .registerType(OAuth2AuthorizationCodeRequestAuthenticationProvider.class, MemberCategory.INVOKE_DECLARED_METHODS); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/package-info.java new file mode 100644 index 0000000000..3922d745fa --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Spring Framework AOT {@link org.springframework.aot.hint.RuntimeHints} for GraalVM + * native images for the authorization server module. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.aot.hint; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java index a548b8df1c..f2c6044761 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java @@ -23,7 +23,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -48,9 +49,9 @@ abstract class AbstractOAuth2AuthorizationCodeRequestAuthenticationToken extends private final Authentication principal; - private final String redirectUri; + private final @Nullable String redirectUri; - private final String state; + private final @Nullable String state; private final Set scopes; @@ -103,8 +104,7 @@ abstract class AbstractOAuth2AuthorizationCodeRequestAuthenticationToken extends * Returns the redirect uri. * @return the redirect uri */ - @Nullable - public String getRedirectUri() { + public @Nullable String getRedirectUri() { return this.redirectUri; } @@ -112,8 +112,7 @@ abstract class AbstractOAuth2AuthorizationCodeRequestAuthenticationToken extends * Returns the state. * @return the state */ - @Nullable - public String getState() { + public @Nullable String getState() { return this.state; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java index 8c1933e3e0..485c424fc1 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/ClientSecretAuthenticationProvider.java @@ -20,6 +20,7 @@ import java.time.Instant; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AuthenticationProvider; @@ -92,7 +93,7 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; // @formatter:off @@ -105,7 +106,7 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP String clientId = clientAuthentication.getPrincipal().toString(); RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); + throw invalidClientException(OAuth2ParameterNames.CLIENT_ID); } if (this.logger.isTraceEnabled()) { @@ -114,26 +115,27 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP if (!registeredClient.getClientAuthenticationMethods() .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); + throw invalidClientException("authentication_method"); } - if (clientAuthentication.getCredentials() == null) { - throwInvalidClient("credentials"); + Object credentials = clientAuthentication.getCredentials(); + if (credentials == null) { + throw invalidClientException("credentials"); } - String clientSecret = clientAuthentication.getCredentials().toString(); + String clientSecret = credentials.toString(); if (!this.passwordEncoder.matches(clientSecret, registeredClient.getClientSecret())) { if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format( "Invalid request: client_secret does not match" + " for registered client '%s'", registeredClient.getId())); } - throwInvalidClient(OAuth2ParameterNames.CLIENT_SECRET); + throw invalidClientException(OAuth2ParameterNames.CLIENT_SECRET); } if (registeredClient.getClientSecretExpiresAt() != null && Instant.now().isAfter(registeredClient.getClientSecretExpiresAt())) { - throwInvalidClient("client_secret_expires_at"); + throw invalidClientException("client_secret_expires_at"); } if (this.passwordEncoder.upgradeEncoding(registeredClient.getClientSecret())) { @@ -164,10 +166,10 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); } - private static void throwInvalidClient(String parameterName) { + private static OAuth2AuthenticationException invalidClientException(String parameterName) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java index 05426cfa8f..7763c91554 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/CodeVerifierAuthenticator.java @@ -24,6 +24,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -65,7 +66,7 @@ final class CodeVerifierAuthenticator { void authenticateRequired(OAuth2ClientAuthenticationToken clientAuthentication, RegisteredClient registeredClient) { if (!authenticate(clientAuthentication, registeredClient)) { - throwInvalidGrant(PkceParameterNames.CODE_VERIFIER); + throw invalidGrantException(PkceParameterNames.CODE_VERIFIER); } } @@ -82,10 +83,11 @@ final class CodeVerifierAuthenticator { return false; } - OAuth2Authorization authorization = this.authorizationService - .findByToken((String) parameters.get(OAuth2ParameterNames.CODE), AUTHORIZATION_CODE_TOKEN_TYPE); + String code = (String) parameters.get(OAuth2ParameterNames.CODE); + Assert.hasText(code, "code cannot be empty"); + OAuth2Authorization authorization = this.authorizationService.findByToken(code, AUTHORIZATION_CODE_TOKEN_TYPE); if (authorization == null) { - throwInvalidGrant(OAuth2ParameterNames.CODE); + throw invalidGrantException(OAuth2ParameterNames.CODE); } if (this.logger.isTraceEnabled()) { @@ -94,6 +96,7 @@ final class CodeVerifierAuthenticator { OAuth2AuthorizationRequest authorizationRequest = authorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); String codeChallenge = (String) authorizationRequest.getAdditionalParameters() .get(PkceParameterNames.CODE_CHALLENGE); @@ -105,7 +108,7 @@ final class CodeVerifierAuthenticator { "Invalid request: code_challenge is required" + " for registered client '%s'", registeredClient.getId())); } - throwInvalidGrant(PkceParameterNames.CODE_CHALLENGE); + throw invalidGrantException(PkceParameterNames.CODE_CHALLENGE); } else { if (this.logger.isTraceEnabled()) { @@ -119,6 +122,7 @@ final class CodeVerifierAuthenticator { this.logger.trace("Validated code verifier parameters"); } + Assert.hasText(codeChallenge, "codeChallenge cannot be empty"); String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters() .get(PkceParameterNames.CODE_CHALLENGE_METHOD); if (!codeVerifierValid(codeVerifier, codeChallenge, codeChallengeMethod)) { @@ -127,7 +131,7 @@ final class CodeVerifierAuthenticator { "Invalid request: code_verifier is missing or invalid" + " for registered client '%s'", registeredClient.getId())); } - throwInvalidGrant(PkceParameterNames.CODE_VERIFIER); + throw invalidGrantException(PkceParameterNames.CODE_VERIFIER); } if (this.logger.isTraceEnabled()) { @@ -143,12 +147,13 @@ final class CodeVerifierAuthenticator { return false; } if (!StringUtils.hasText((String) parameters.get(OAuth2ParameterNames.CODE))) { - throwInvalidGrant(OAuth2ParameterNames.CODE); + throw invalidGrantException(OAuth2ParameterNames.CODE); } return true; } - private boolean codeVerifierValid(String codeVerifier, String codeChallenge, String codeChallengeMethod) { + private boolean codeVerifierValid(@Nullable String codeVerifier, String codeChallenge, + @Nullable String codeChallengeMethod) { if (!StringUtils.hasText(codeVerifier)) { return false; } @@ -169,10 +174,10 @@ final class CodeVerifierAuthenticator { return false; } - private static void throwInvalidGrant(String parameterName) { + private static OAuth2AuthenticationException invalidGrantException(String parameterName) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT, "Client authentication failed: " + parameterName, null); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java index 7d8af7acd5..e549a1053e 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/DPoPProofVerifier.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; @@ -24,6 +26,7 @@ import org.springframework.security.oauth2.jwt.DPoPProofJwtDecoderFactory; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -42,14 +45,17 @@ final class DPoPProofVerifier { private DPoPProofVerifier() { } - static Jwt verifyIfAvailable(OAuth2AuthorizationGrantAuthenticationToken authorizationGrantAuthentication) { + static @Nullable Jwt verifyIfAvailable( + OAuth2AuthorizationGrantAuthenticationToken authorizationGrantAuthentication) { String dPoPProof = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_proof"); if (!StringUtils.hasText(dPoPProof)) { return null; } String method = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_method"); + Assert.hasText(method, "dpop_method cannot be empty"); String targetUri = (String) authorizationGrantAuthentication.getAdditionalParameters().get("dpop_target_uri"); + Assert.hasText(targetUri, "dpop_target_uri cannot be empty"); Jwt dPoPProofJwt; try { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java index 06efbe8df4..ad2666b652 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/JwtClientAssertionAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -82,7 +83,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; if (!JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD.equals(clientAuthentication.getClientAuthenticationMethod())) { @@ -92,7 +93,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic String clientId = clientAuthentication.getPrincipal().toString(); RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); + throw invalidClientException(OAuth2ParameterNames.CLIENT_ID); } if (this.logger.isTraceEnabled()) { @@ -102,21 +103,22 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic // @formatter:off if (!registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.PRIVATE_KEY_JWT) && !registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) { - throwInvalidClient("authentication_method"); + throw invalidClientException("authentication_method"); } // @formatter:on - if (clientAuthentication.getCredentials() == null) { - throwInvalidClient("credentials"); + Object credentials = clientAuthentication.getCredentials(); + if (credentials == null) { + throw invalidClientException("credentials"); } Jwt jwtAssertion = null; JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(registeredClient); try { - jwtAssertion = jwtDecoder.decode(clientAuthentication.getCredentials().toString()); + jwtAssertion = jwtDecoder.decode(credentials.toString()); } catch (JwtException ex) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ASSERTION, ex); + throw invalidClientException(OAuth2ParameterNames.CLIENT_ASSERTION, ex); } if (this.logger.isTraceEnabled()) { @@ -159,14 +161,15 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic this.jwtDecoderFactory = jwtDecoderFactory; } - private static void throwInvalidClient(String parameterName) { - throwInvalidClient(parameterName, null); + private static OAuth2AuthenticationException invalidClientException(String parameterName) { + return invalidClientException(parameterName, null); } - private static void throwInvalidClient(String parameterName, Throwable cause) { + private static OAuth2AuthenticationException invalidClientException(String parameterName, + @Nullable Throwable cause) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error, error.toString(), cause); + return new OAuth2AuthenticationException(error, error.toString(), cause); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java index 9fafb2c7ff..a2607eb549 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AccessTokenResponseAuthenticationSuccessHandler; import org.springframework.util.Assert; @@ -47,9 +48,8 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -65,7 +65,9 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe * @return the {@link OAuth2AccessTokenResponse.Builder} */ public OAuth2AccessTokenResponse.Builder getAccessTokenResponse() { - return get(OAuth2AccessTokenResponse.Builder.class); + OAuth2AccessTokenResponse.Builder accessTokenResponse = get(OAuth2AccessTokenResponse.Builder.class); + Assert.notNull(accessTokenResponse, "accessTokenResponse cannot be null"); + return accessTokenResponse; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java index 4bd12b1750..4304e850da 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java @@ -20,7 +20,8 @@ import java.io.Serial; import java.util.Collections; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -52,7 +53,7 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication private final OAuth2AccessToken accessToken; - private final OAuth2RefreshToken refreshToken; + private final @Nullable OAuth2RefreshToken refreshToken; private final Map additionalParameters; @@ -135,8 +136,7 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication * Returns the {@link OAuth2RefreshToken refresh token}. * @return the {@link OAuth2RefreshToken} or {@code null} if not available */ - @Nullable - public OAuth2RefreshToken getRefreshToken() { + public @Nullable OAuth2RefreshToken getRefreshToken() { return this.refreshToken; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java index 6591ca135b..95109752ca 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationContext.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.authorization.context.Context; @@ -42,7 +44,9 @@ public interface OAuth2AuthenticationContext extends Context { */ @SuppressWarnings("unchecked") default T getAuthentication() { - return (T) get(Authentication.class); + Authentication authentication = get(Authentication.class); + Assert.notNull(authentication, "authentication cannot be null"); + return (T) authentication; } /** @@ -85,7 +89,7 @@ public interface OAuth2AuthenticationContext extends Context { } @SuppressWarnings("unchecked") - protected V get(Object key) { + protected @Nullable V get(Object key) { return (V) getContext().get(key); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java index 3cddb148f5..5adc6146c4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java @@ -43,8 +43,9 @@ final class OAuth2AuthenticationProviderUtils { static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) { OAuth2ClientAuthenticationToken clientPrincipal = null; - if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) { - clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal(); + Object principal = authentication.getPrincipal(); + if (principal != null && OAuth2ClientAuthenticationToken.class.isAssignableFrom(principal.getClass())) { + clientPrincipal = (OAuth2ClientAuthenticationToken) principal; } if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { return clientPrincipal; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java index 1cb945f0d0..2afa645cfb 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java @@ -30,6 +30,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AuthenticationProvider; @@ -96,7 +97,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth private final OAuth2TokenGenerator tokenGenerator; - private SessionRegistry sessionRegistry; + private @Nullable SessionRegistry sessionRegistry; /** * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the @@ -119,6 +120,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -136,9 +138,11 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth OAuth2Authorization.Token authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); + Assert.notNull(authorizationCode, "authorizationCode cannot be null"); OAuth2AuthorizationRequest authorizationRequest = authorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) { if (!authorizationCode.isInvalidated()) { @@ -193,6 +197,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth } Authentication principal = authorization.getAttribute(Principal.class.getName()); + Assert.notNull(principal, "principal cannot be null"); // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() @@ -331,10 +336,14 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth this.sessionRegistry = sessionRegistry; } - private SessionInformation getSessionInformation(Authentication principal) { + private @Nullable SessionInformation getSessionInformation(Authentication principal) { SessionInformation sessionInformation = null; if (this.sessionRegistry != null) { - List sessions = this.sessionRegistry.getAllSessions(principal.getPrincipal(), false); + Object sessionPrincipal = principal.getPrincipal(); + if (sessionPrincipal == null) { + return null; + } + List sessions = this.sessionRegistry.getAllSessions(sessionPrincipal, false); if (!CollectionUtils.isEmpty(sessions)) { sessionInformation = sessions.get(0); if (sessions.size() > 1) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java index 74e9825a0a..b4869952af 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java @@ -18,7 +18,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; @@ -38,7 +39,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2Authorizat private final String code; - private final String redirectUri; + private final @Nullable String redirectUri; /** * Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided @@ -68,8 +69,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2Authorizat * Returns the redirect uri. * @return the redirect uri */ - @Nullable - public String getRedirectUri() { + public @Nullable String getRedirectUri() { return this.redirectUri; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java index aa03e4fdd1..4310db9756 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeGenerator.java @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.time.Instant; import java.util.Base64; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; @@ -42,10 +43,9 @@ final class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -67,15 +67,16 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** * Returns the {@link OAuth2AuthorizationRequest authorization request}. * @return the {@link OAuth2AuthorizationRequest} */ - @Nullable - public OAuth2AuthorizationRequest getAuthorizationRequest() { + public @Nullable OAuth2AuthorizationRequest getAuthorizationRequest() { return get(OAuth2AuthorizationRequest.class); } @@ -83,8 +84,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement * Returns the {@link OAuth2AuthorizationConsent authorization consent}. * @return the {@link OAuth2AuthorizationConsent} */ - @Nullable - public OAuth2AuthorizationConsent getAuthorizationConsent() { + public @Nullable OAuth2AuthorizationConsent getAuthorizationConsent() { return get(OAuth2AuthorizationConsent.class); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java index 023f2a2bb0..49bad93d54 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationException.java @@ -16,7 +16,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -33,7 +34,7 @@ import org.springframework.security.oauth2.core.OAuth2Error; */ public class OAuth2AuthorizationCodeRequestAuthenticationException extends OAuth2AuthenticationException { - private final OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication; + private final @Nullable OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication; /** * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationException} using @@ -67,8 +68,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationException extends OAuth * (or Consent), or {@code null} if not available. * @return the {@link OAuth2AuthorizationCodeRequestAuthenticationToken} */ - @Nullable - public OAuth2AuthorizationCodeRequestAuthenticationToken getAuthorizationCodeRequestAuthentication() { + public @Nullable OAuth2AuthorizationCodeRequestAuthenticationToken getAuthorizationCodeRequestAuthentication() { return this.authorizationCodeRequestAuthentication; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java index 6c3fd90eff..7cdc21f013 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java @@ -30,6 +30,7 @@ import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -129,19 +130,19 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters() .get(OAuth2ParameterNames.REQUEST_URI); if (StringUtils.hasText(requestUri)) { - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = null; + OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri; try { pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri.parse(requestUri); } catch (Exception ex) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, authorizationCodeRequestAuthentication, null); } pushedAuthorization = this.authorizationService.findByToken(pushedAuthorizationRequestUri.getState(), STATE_TOKEN_TYPE); if (pushedAuthorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, authorizationCodeRequestAuthentication, null); } @@ -151,9 +152,10 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen OAuth2AuthorizationRequest authorizationRequest = pushedAuthorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); if (!authorizationCodeRequestAuthentication.getClientId().equals(authorizationRequest.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, authorizationCodeRequestAuthentication, null); } @@ -165,7 +167,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen .warn(LogMessage.format("Removed expired pushed authorization request for client id '%s'", authorizationRequest.getClientId())); } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI, authorizationCodeRequestAuthentication, null); } @@ -179,7 +181,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(authorizationCodeRequestAuthentication.getClientId()); if (registeredClient == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, authorizationCodeRequestAuthentication, null); } @@ -233,11 +235,12 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen if (!isPrincipalAuthenticated(principal)) { if (promptValues.contains(OidcPrompt.NONE)) { - throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient); + throw createException("login_required", "prompt", authorizationCodeRequestAuthentication, + registeredClient); } else { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "principal", authorizationCodeRequestAuthentication, - registeredClient); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, "principal", + authorizationCodeRequestAuthentication, registeredClient); } } @@ -260,7 +263,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen if (this.authorizationConsentRequired.test(authenticationContextBuilder.build())) { if (promptValues.contains(OidcPrompt.NONE)) { // Return an error instead of displaying the consent page - throwError("consent_required", "prompt", authorizationCodeRequestAuthentication, registeredClient); + throw createException("consent_required", "prompt", authorizationCodeRequestAuthentication, + registeredClient); } String state = DEFAULT_STATE_GENERATOR.generateKey(); @@ -416,15 +420,17 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) { return false; } + OAuth2AuthorizationRequest authorizationRequest = authenticationContext.getAuthorizationRequest(); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); // 'openid' scope does not require consent - if (authenticationContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID) - && authenticationContext.getAuthorizationRequest().getScopes().size() == 1) { + if (authorizationRequest.getScopes().contains(OidcScopes.OPENID) + && authorizationRequest.getScopes().size() == 1) { return false; } if (authenticationContext.getAuthorizationConsent() != null && authenticationContext.getAuthorizationConsent() .getScopes() - .containsAll(authenticationContext.getAuthorizationRequest().getScopes())) { + .containsAll(authorizationRequest.getScopes())) { return false; } @@ -442,7 +448,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen private static OAuth2TokenContext createAuthorizationCodeTokenContext( OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2Authorization authorization, Set authorizedScopes) { + RegisteredClient registeredClient, @Nullable OAuth2Authorization authorization, + Set authorizedScopes) { // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() @@ -467,23 +474,27 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen && principal.isAuthenticated(); } - private static void throwError(String errorCode, String parameterName, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(String errorCode, + String parameterName, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient) { - throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient, null); + @Nullable RegisteredClient registeredClient) { + return createException(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, + registeredClient, null); } - private static void throwError(String errorCode, String parameterName, String errorUri, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(String errorCode, + String parameterName, String errorUri, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { + @Nullable RegisteredClient registeredClient, @Nullable OAuth2AuthorizationRequest authorizationRequest) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient, + return createException(error, parameterName, authorizationCodeRequestAuthentication, registeredClient, authorizationRequest); } - private static void throwError(OAuth2Error error, String parameterName, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(OAuth2Error error, + String parameterName, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { + @Nullable RegisteredClient registeredClient, @Nullable OAuth2AuthorizationRequest authorizationRequest) { String redirectUri = resolveRedirectUri(authorizationCodeRequestAuthentication, authorizationRequest, registeredClient); @@ -500,13 +511,13 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(), authorizationCodeRequestAuthentication.getAdditionalParameters()); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, + return new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); } - private static String resolveRedirectUri( + private static @Nullable String resolveRedirectUri( OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, - OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) { + @Nullable OAuth2AuthorizationRequest authorizationRequest, @Nullable RegisteredClient registeredClient) { if (authorizationCodeRequestAuthentication != null && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java index bf8d0445d7..60f4aaa5fb 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationToken.java @@ -20,7 +20,8 @@ import java.io.Serial; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.util.Assert; @@ -40,7 +41,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken @Serial private static final long serialVersionUID = -1946164725241393094L; - private final OAuth2AuthorizationCode authorizationCode; + private final @Nullable OAuth2AuthorizationCode authorizationCode; private boolean validated; @@ -86,8 +87,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken * Returns the {@link OAuth2AuthorizationCode}. * @return the {@link OAuth2AuthorizationCode} */ - @Nullable - public OAuth2AuthorizationCode getAuthorizationCode() { + public @Nullable OAuth2AuthorizationCode getAuthorizationCode() { return this.authorizationCode; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java index 08af27f02f..839ae289c0 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationValidator.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.core.Authentication; @@ -104,7 +105,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator "Invalid request: requested grant_type is not allowed for registered client '%s'", registeredClient.getId())); } - throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID, + throw createException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID, authorizationCodeRequestAuthentication, registeredClient); } } @@ -130,7 +131,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator LOGGER.debug(LogMessage.format("Invalid request: redirect_uri is missing or contains a fragment" + " for registered client '%s'", registeredClient.getId())); } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, authorizationCodeRequestAuthentication, registeredClient); } @@ -140,7 +141,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator // When comparing client redirect URIs against pre-registered URIs, // authorization servers MUST utilize exact string matching. if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, authorizationCodeRequestAuthentication, registeredClient); } } @@ -166,7 +167,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator "Invalid request: redirect_uri does not match for registered client '%s'", registeredClient.getId())); } - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, authorizationCodeRequestAuthentication, registeredClient); } } @@ -178,7 +179,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) || registeredClient.getRedirectUris().size() != 1) { // redirect_uri is REQUIRED for OpenID Connect - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, authorizationCodeRequestAuthentication, registeredClient); } } @@ -197,7 +198,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator LogMessage.format("Invalid request: requested scope is not allowed for registered client '%s'", registeredClient.getId())); } - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, + throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationCodeRequestAuthentication, registeredClient); } } @@ -215,12 +216,12 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator String codeChallengeMethod = (String) authorizationCodeRequestAuthentication.getAdditionalParameters() .get(PkceParameterNames.CODE_CHALLENGE_METHOD); if (!StringUtils.hasText(codeChallengeMethod) || !"S256".equals(codeChallengeMethod)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI, - authorizationCodeRequestAuthentication, registeredClient); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, + PKCE_ERROR_URI, authorizationCodeRequestAuthentication, registeredClient); } } else if (registeredClient.getClientSettings().isRequireProofKey()) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI, + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI, authorizationCodeRequestAuthentication, registeredClient); } } @@ -239,15 +240,15 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator if (promptValues.contains(OidcPrompt.NONE)) { if (promptValues.contains(OidcPrompt.LOGIN) || promptValues.contains(OidcPrompt.CONSENT) || promptValues.contains(OidcPrompt.SELECT_ACCOUNT)) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt", authorizationCodeRequestAuthentication, - registeredClient); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, "prompt", + authorizationCodeRequestAuthentication, registeredClient); } } } } } - private static boolean isLoopbackAddress(String host) { + private static boolean isLoopbackAddress(@Nullable String host) { if (!StringUtils.hasText(host)) { return false; } @@ -273,20 +274,24 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator } } - private static void throwError(String errorCode, String parameterName, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(String errorCode, + String parameterName, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, RegisteredClient registeredClient) { - throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient); + return createException(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, + registeredClient); } - private static void throwError(String errorCode, String parameterName, String errorUri, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(String errorCode, + String parameterName, String errorUri, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, RegisteredClient registeredClient) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); - throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient); + return createException(error, parameterName, authorizationCodeRequestAuthentication, registeredClient); } - private static void throwError(OAuth2Error error, String parameterName, + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(OAuth2Error error, + String parameterName, OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, RegisteredClient registeredClient) { @@ -306,7 +311,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator authorizationCodeRequestAuthentication.getAdditionalParameters()); authorizationCodeRequestAuthenticationResult.setAuthenticated(true); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, + return new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java index 75a4f4f852..6859a00cc4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; @@ -50,9 +51,8 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -68,7 +68,9 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA * @return the {@link OAuth2AuthorizationConsent.Builder} */ public OAuth2AuthorizationConsent.Builder getAuthorizationConsent() { - return get(OAuth2AuthorizationConsent.Builder.class); + OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = get(OAuth2AuthorizationConsent.Builder.class); + Assert.notNull(authorizationConsentBuilder, "authorizationConsentBuilder cannot be null"); + return authorizationConsentBuilder; } /** @@ -76,7 +78,9 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** @@ -84,14 +88,16 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA * @return the {@link OAuth2Authorization} */ public OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); + OAuth2Authorization authorization = get(OAuth2Authorization.class); + Assert.notNull(authorization, "authorization cannot be null"); + return authorization; } /** * Returns the {@link OAuth2AuthorizationRequest authorization request}. * @return the {@link OAuth2AuthorizationRequest} */ - public OAuth2AuthorizationRequest getAuthorizationRequest() { + public @Nullable OAuth2AuthorizationRequest getAuthorizationRequest() { return get(OAuth2AuthorizationRequest.class); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java index 539c54d28e..85b73bb79a 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; @@ -79,7 +80,7 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A private OAuth2TokenGenerator authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator(); - private Consumer authorizationConsentCustomizer; + private @Nullable Consumer authorizationConsentCustomizer; /** * Constructs an {@code OAuth2AuthorizationConsentAuthenticationProvider} using the @@ -100,7 +101,7 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication instanceof OAuth2DeviceAuthorizationConsentAuthenticationToken) { // This is NOT an OAuth 2.0 Authorization Consent for the Authorization Code // Grant, @@ -114,8 +115,8 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A OAuth2Authorization authorization = this.authorizationService .findByToken(authorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE); if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, authorizationConsentAuthentication, - null, null); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, + authorizationConsentAuthentication, null, null); } if (this.logger.isTraceEnabled()) { @@ -125,14 +126,18 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A // The 'in-flight' authorization must be associated to the current principal Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal(); if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, authorizationConsentAuthentication, - null, null); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, + authorizationConsentAuthentication, null, null); } RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(authorizationConsentAuthentication.getClientId()); - if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, + if (registeredClient == null) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, + authorizationConsentAuthentication, null, null); + } + if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, authorizationConsentAuthentication, registeredClient, null); } @@ -142,11 +147,12 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A OAuth2AuthorizationRequest authorizationRequest = authorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); Set requestedScopes = authorizationRequest.getScopes(); Set authorizedScopes = new HashSet<>(authorizationConsentAuthentication.getScopes()); if (!requestedScopes.containsAll(authorizedScopes)) { - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationConsentAuthentication, - registeredClient, authorizationRequest); + throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, + authorizationConsentAuthentication, registeredClient, authorizationRequest); } if (this.logger.isTraceEnabled()) { @@ -215,12 +221,12 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A if (this.logger.isTraceEnabled()) { this.logger.trace("Removed authorization"); } - throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, + throw createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationConsentAuthentication, registeredClient, authorizationRequest); } OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build(); - if (!authorizationConsent.equals(currentAuthorizationConsent)) { + if (currentAuthorizationConsent == null || !authorizationConsent.equals(currentAuthorizationConsent)) { this.authorizationConsentService.save(authorizationConsent); if (this.logger.isTraceEnabled()) { this.logger.trace("Saved authorization consent"); @@ -334,16 +340,17 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A && principal.isAuthenticated(); } - private static void throwError(String errorCode, String parameterName, - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(String errorCode, + String parameterName, OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, + @Nullable RegisteredClient registeredClient, @Nullable OAuth2AuthorizationRequest authorizationRequest) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); - throwError(error, parameterName, authorizationConsentAuthentication, registeredClient, authorizationRequest); + return createException(error, parameterName, authorizationConsentAuthentication, registeredClient, + authorizationRequest); } - private static void throwError(OAuth2Error error, String parameterName, - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, - RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { + private static OAuth2AuthorizationCodeRequestAuthenticationException createException(OAuth2Error error, + String parameterName, OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication, + @Nullable RegisteredClient registeredClient, @Nullable OAuth2AuthorizationRequest authorizationRequest) { String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient); if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) @@ -363,12 +370,12 @@ public final class OAuth2AuthorizationConsentAuthenticationProvider implements A (Authentication) authorizationConsentAuthentication.getPrincipal(), redirectUri, state, requestedScopes, null); - throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, + return new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); } - private static String resolveRedirectUri(OAuth2AuthorizationRequest authorizationRequest, - RegisteredClient registeredClient) { + private static @Nullable String resolveRedirectUri(@Nullable OAuth2AuthorizationRequest authorizationRequest, + @Nullable RegisteredClient registeredClient) { if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) { return authorizationRequest.getRedirectUri(); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java index e5772e6b26..7e970292a9 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationToken.java @@ -23,7 +23,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java index 60733c6785..cf236ced8c 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java @@ -21,7 +21,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java index 6cd5d06f5e..1ffad80f66 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.util.Assert; @@ -45,9 +46,8 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -62,7 +62,9 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java index 642467be11..b96dd62cdc 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java @@ -20,7 +20,8 @@ import java.io.Serial; import java.util.Collections; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.Transient; @@ -49,11 +50,11 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken private final String clientId; - private final RegisteredClient registeredClient; + private final @Nullable RegisteredClient registeredClient; private final ClientAuthenticationMethod clientAuthenticationMethod; - private final Object credentials; + private final @Nullable Object credentials; private final Map additionalParameters; @@ -103,9 +104,8 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken return this.clientId; } - @Nullable @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return this.credentials; } @@ -115,8 +115,7 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken * @return the authenticated {@link RegisteredClient}, or {@code null} if not * authenticated */ - @Nullable - public RegisteredClient getRegisteredClient() { + public @Nullable RegisteredClient getRegisteredClient() { return this.registeredClient; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java index 35b0ee37a8..abf2b9ae93 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.util.Assert; @@ -45,9 +46,8 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -62,7 +62,9 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java index 8ac7917a83..7275cffad2 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java @@ -95,6 +95,7 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(clientCredentialsAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java index f634cfa4f5..7e72d5fb39 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java @@ -21,7 +21,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationProvider.java index 54a910ac0a..fb08300f84 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationProvider.java @@ -21,10 +21,12 @@ import java.net.URISyntaxException; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AuthenticationProvider; @@ -138,6 +140,7 @@ public final class OAuth2ClientRegistrationAuthenticationProvider implements Aut } OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); + Assert.notNull(authorizedAccessToken, "accessToken cannot be null"); if (!authorizedAccessToken.isActive()) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); } @@ -199,9 +202,10 @@ public final class OAuth2ClientRegistrationAuthenticationProvider implements Aut private OAuth2ClientRegistrationAuthenticationToken registerClient( OAuth2ClientRegistrationAuthenticationToken clientRegistrationAuthentication, - OAuth2Authorization authorization) { + @Nullable OAuth2Authorization authorization) { - if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) { + List redirectUris = clientRegistrationAuthentication.getClientRegistration().getRedirectUris(); + if (!isValidRedirectUris((redirectUris != null) ? redirectUris : Collections.emptyList())) { throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI, OAuth2ClientMetadataClaimNames.REDIRECT_URIS); } @@ -236,8 +240,10 @@ public final class OAuth2ClientRegistrationAuthenticationProvider implements Aut if (authorization != null) { // Invalidate the "initial" access token as it can only be used once + OAuth2Authorization.Token accessToken = authorization.getAccessToken(); + Assert.notNull(accessToken, "accessToken cannot be null"); OAuth2Authorization.Builder builder = OAuth2Authorization.from(authorization) - .invalidate(authorization.getAccessToken().getToken()); + .invalidate(accessToken.getToken()); if (authorization.getRefreshToken() != null) { builder.invalidate(authorization.getRefreshToken().getToken()); } @@ -265,8 +271,9 @@ public final class OAuth2ClientRegistrationAuthenticationProvider implements Aut private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) { Collection authorizedScope = Collections.emptySet(); - if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { - authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); + Map claims = authorizedAccessToken.getClaims(); + if (claims != null && claims.containsKey(OAuth2ParameterNames.SCOPE)) { + authorizedScope = (Collection) claims.get(OAuth2ParameterNames.SCOPE); } if (!authorizedScope.containsAll(requiredScope)) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationToken.java index 5204182052..2d489a091d 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientRegistrationAuthenticationToken.java @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.io.Serial; import java.util.Collections; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; @@ -40,8 +41,7 @@ public class OAuth2ClientRegistrationAuthenticationToken extends AbstractAuthent @Serial private static final long serialVersionUID = 7135429161909989115L; - @Nullable - private final Authentication principal; + private final @Nullable Authentication principal; private final OAuth2ClientRegistration clientRegistration; @@ -62,9 +62,8 @@ public class OAuth2ClientRegistrationAuthenticationToken extends AbstractAuthent } } - @Nullable @Override - public Object getPrincipal() { + public @Nullable Object getPrincipal() { return this.principal; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java index 385dd11124..2d3ee05d88 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java @@ -23,6 +23,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; @@ -72,7 +73,7 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem private final OAuth2AuthorizationConsentService authorizationConsentService; - private Consumer authorizationConsentCustomizer; + private @Nullable Consumer authorizationConsentCustomizer; /** * Constructs an {@code OAuth2DeviceAuthorizationConsentAuthenticationProvider} using @@ -99,7 +100,7 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem OAuth2Authorization authorization = this.authorizationService .findByToken(deviceAuthorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE); if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); } if (this.logger.isTraceEnabled()) { @@ -109,13 +110,13 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem // The authorization must be associated to the current principal Authentication principal = (Authentication) deviceAuthorizationConsentAuthentication.getPrincipal(); if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); } RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(deviceAuthorizationConsentAuthentication.getClientId()); if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); } if (this.logger.isTraceEnabled()) { @@ -123,9 +124,10 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem } Set requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); + Assert.notNull(requestedScopes, "requestedScopes cannot be null"); Set authorizedScopes = new HashSet<>(deviceAuthorizationConsentAuthentication.getScopes()); if (!requestedScopes.containsAll(authorizedScopes)) { - throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); + throw createException(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE); } if (this.logger.isTraceEnabled()) { @@ -177,7 +179,9 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem authorizationConsentBuilder.authorities(authorities::addAll); OAuth2Authorization.Token deviceCodeToken = authorization.getToken(OAuth2DeviceCode.class); + Assert.notNull(deviceCodeToken, "deviceCode cannot be null"); OAuth2Authorization.Token userCodeToken = authorization.getToken(OAuth2UserCode.class); + Assert.notNull(userCodeToken, "userCode cannot be null"); if (authorities.isEmpty()) { // Authorization consent denied (or revoked) @@ -196,11 +200,11 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem if (this.logger.isTraceEnabled()) { this.logger.trace("Invalidated device code and user code because authorization consent was denied"); } - throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID); + throw createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID); } OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build(); - if (!authorizationConsent.equals(currentAuthorizationConsent)) { + if (currentAuthorizationConsent == null || !authorizationConsent.equals(currentAuthorizationConsent)) { this.authorizationConsentService.save(authorizationConsent); if (this.logger.isTraceEnabled()) { this.logger.trace("Saved authorization consent"); @@ -263,9 +267,9 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationProvider implem && principal.isAuthenticated(); } - private static void throwError(String errorCode, String parameterName) { + private static OAuth2AuthenticationException createException(String errorCode, String parameterName) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java index 99479d74df..9ec7fa3ae5 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationToken.java @@ -22,7 +22,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -43,7 +44,7 @@ public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2A private final String userCode; - private final Set requestedScopes; + private final @Nullable Set requestedScopes; /** * Constructs an {@code OAuth2DeviceAuthorizationConsentAuthenticationToken} using the @@ -98,9 +99,9 @@ public class OAuth2DeviceAuthorizationConsentAuthenticationToken extends OAuth2A /** * Returns the requested scopes. - * @return the requested scopes + * @return the requested scopes, or {@code null} if not available */ - public Set getRequestedScopes() { + public @Nullable Set getRequestedScopes() { return this.requestedScopes; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java index c879cf2a86..1a6e373624 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationProvider.java @@ -23,9 +23,9 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; -import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -101,6 +101,7 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(deviceAuthorizationRequestAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -224,9 +225,8 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem private final StringKeyGenerator deviceCodeGenerator = new Base64StringKeyGenerator( Base64.getUrlEncoder().withoutPadding(), 96); - @Nullable @Override - public OAuth2DeviceCode generate(OAuth2TokenContext context) { + public @Nullable OAuth2DeviceCode generate(OAuth2TokenContext context) { if (context.getTokenType() == null || !OAuth2ParameterNames.DEVICE_CODE.equals(context.getTokenType().getValue())) { return null; @@ -268,9 +268,8 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem private final StringKeyGenerator userCodeGenerator = new UserCodeStringKeyGenerator(); - @Nullable @Override - public OAuth2UserCode generate(OAuth2TokenContext context) { + public @Nullable OAuth2UserCode generate(OAuth2TokenContext context) { if (context.getTokenType() == null || !OAuth2ParameterNames.USER_CODE.equals(context.getTokenType().getValue())) { return null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java index 7fc2ae95b9..eaf85e0812 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationRequestAuthenticationToken.java @@ -23,7 +23,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2DeviceCode; @@ -47,13 +48,13 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac private final Authentication clientPrincipal; - private final String authorizationUri; + private final @Nullable String authorizationUri; private final Set scopes; - private final OAuth2DeviceCode deviceCode; + private final @Nullable OAuth2DeviceCode deviceCode; - private final OAuth2UserCode userCode; + private final @Nullable OAuth2UserCode userCode; private final Map additionalParameters; @@ -116,7 +117,7 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac * Returns the authorization {@code URI}. * @return the authorization {@code URI} */ - public String getAuthorizationUri() { + public @Nullable String getAuthorizationUri() { return this.authorizationUri; } @@ -132,7 +133,7 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac * Returns the device code. * @return the device code */ - public OAuth2DeviceCode getDeviceCode() { + public @Nullable OAuth2DeviceCode getDeviceCode() { return this.deviceCode; } @@ -140,7 +141,7 @@ public class OAuth2DeviceAuthorizationRequestAuthenticationToken extends Abstrac * Returns the user code. * @return the user code */ - public OAuth2UserCode getUserCode() { + public @Nullable OAuth2UserCode getUserCode() { return this.userCode; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java index beca3134e0..0d3de0178f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java @@ -104,6 +104,7 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(deviceCodeAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -119,8 +120,8 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat this.logger.trace("Retrieved authorization with device code"); } - OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); OAuth2Authorization.Token deviceCode = authorization.getToken(OAuth2DeviceCode.class); + Assert.notNull(deviceCode, "deviceCode cannot be null"); if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { if (!deviceCode.isInvalidated()) { @@ -158,6 +159,9 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat throw new OAuth2AuthenticationException(error); } + OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); + Assert.notNull(userCode, "userCode cannot be null"); + // authorization_pending // The authorization request is still pending as the end user hasn't // yet completed the user-interaction steps (Section 3.3). The @@ -193,10 +197,13 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat this.logger.trace("Validated device token request parameters"); } + Authentication principal = authorization.getAttribute(Principal.class.getName()); + Assert.notNull(principal, "principal cannot be null"); + // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) - .principal(authorization.getAttribute(Principal.class.getName())) + .principal(principal) .authorizationServerContext(AuthorizationServerContextHolder.getContext()) .authorization(authorization) .authorizedScopes(authorization.getAuthorizedScopes()) diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java index 653ca0b62c..2022c77a23 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationToken.java @@ -18,7 +18,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationContext.java index 7f3fcea96c..b2ea7c6c62 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; @@ -48,9 +49,8 @@ public final class OAuth2DeviceVerificationAuthenticationContext implements OAut } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -65,7 +65,9 @@ public final class OAuth2DeviceVerificationAuthenticationContext implements OAut * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** @@ -73,15 +75,16 @@ public final class OAuth2DeviceVerificationAuthenticationContext implements OAut * @return the {@link OAuth2Authorization} */ public OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); + OAuth2Authorization authorization = get(OAuth2Authorization.class); + Assert.notNull(authorization, "authorization cannot be null"); + return authorization; } /** * Returns the {@link OAuth2AuthorizationConsent authorization consent}. * @return the {@link OAuth2AuthorizationConsent}, or {@code null} if not available */ - @Nullable - public OAuth2AuthorizationConsent getAuthorizationConsent() { + public @Nullable OAuth2AuthorizationConsent getAuthorizationConsent() { return get(OAuth2AuthorizationConsent.class); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java index b047e8007c..307278a2c1 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.security.Principal; import java.util.Base64; +import java.util.Collections; import java.util.Set; import java.util.function.Predicate; @@ -115,6 +116,7 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut } OAuth2Authorization.Token userCode = authorization.getToken(OAuth2UserCode.class); + Assert.notNull(userCode, "userCode cannot be null"); if (!userCode.isActive()) { if (!userCode.isInvalidated()) { authorization = OAuth2Authorization.from(authorization).invalidate(userCode.getToken()).build(); @@ -137,12 +139,16 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut RegisteredClient registeredClient = this.registeredClientRepository .findById(authorization.getRegisteredClientId()); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); } Set requestedScopes = authorization.getAttribute(OAuth2ParameterNames.SCOPE); + if (requestedScopes == null) { + requestedScopes = Collections.emptySet(); + } OAuth2DeviceVerificationAuthenticationContext.Builder authenticationContextBuilder = OAuth2DeviceVerificationAuthenticationContext .with(deviceVerificationAuthentication) @@ -174,7 +180,7 @@ public final class OAuth2DeviceVerificationAuthenticationProvider implements Aut } Set currentAuthorizedScopes = (currentAuthorizationConsent != null) - ? currentAuthorizationConsent.getScopes() : null; + ? currentAuthorizationConsent.getScopes() : Collections.emptySet(); AuthorizationServerSettings authorizationServerSettings = AuthorizationServerContextHolder.getContext() .getAuthorizationServerSettings(); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java index af948ca6dd..784fd25996 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationToken.java @@ -21,7 +21,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -46,7 +47,7 @@ public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthent private final Map additionalParameters; - private final String clientId; + private final @Nullable String clientId; /** * Constructs an {@code OAuth2DeviceVerificationAuthenticationToken} using the @@ -114,9 +115,9 @@ public class OAuth2DeviceVerificationAuthenticationToken extends AbstractAuthent /** * Returns the client identifier. - * @return the client identifier + * @return the client identifier, or {@code null} if not set */ - public String getClientId() { + public @Nullable String getClientId() { return this.clientId; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java index 4d0225a27b..7a7599bc15 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationProvider.java @@ -74,6 +74,7 @@ public final class OAuth2PushedAuthorizationRequestAuthenticationProvider implem OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(pushedAuthorizationRequestAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java index 73b94bfa86..8de7b334c0 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java @@ -21,7 +21,8 @@ import java.time.Instant; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -39,9 +40,9 @@ public class OAuth2PushedAuthorizationRequestAuthenticationToken @Serial private static final long serialVersionUID = 7330534287786569644L; - private final String requestUri; + private final @Nullable String requestUri; - private final Instant requestUriExpiresAt; + private final @Nullable Instant requestUriExpiresAt; /** * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationToken} using the @@ -91,8 +92,7 @@ public class OAuth2PushedAuthorizationRequestAuthenticationToken * Returns the {@code request_uri} corresponding to the authorization request posted. * @return the {@code request_uri} corresponding to the authorization request posted */ - @Nullable - public String getRequestUri() { + public @Nullable String getRequestUri() { return this.requestUri; } @@ -102,8 +102,7 @@ public class OAuth2PushedAuthorizationRequestAuthenticationToken * @return the expiration time on or after which the {@code request_uri} MUST NOT be * accepted */ - @Nullable - public Instant getRequestUriExpiresAt() { + public @Nullable Instant getRequestUriExpiresAt() { return this.requestUriExpiresAt; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java index 96566f0b6f..39fb98d7cf 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java @@ -38,11 +38,11 @@ final class OAuth2PushedAuthorizationRequestUri { private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator( Base64.getUrlEncoder()); - private String requestUri; + private final String requestUri; - private String state; + private final String state; - private Instant expiresAt; + private final Instant expiresAt; static OAuth2PushedAuthorizationRequestUri create() { return create(Instant.now().plusSeconds(300)); @@ -50,23 +50,17 @@ final class OAuth2PushedAuthorizationRequestUri { static OAuth2PushedAuthorizationRequestUri create(Instant expiresAt) { String state = DEFAULT_STATE_GENERATOR.generateKey(); - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri(); - pushedAuthorizationRequestUri.requestUri = REQUEST_URI_PREFIX + state + REQUEST_URI_DELIMITER - + expiresAt.toEpochMilli(); - pushedAuthorizationRequestUri.state = state + REQUEST_URI_DELIMITER + expiresAt.toEpochMilli(); - pushedAuthorizationRequestUri.expiresAt = expiresAt; - return pushedAuthorizationRequestUri; + String requestUri = REQUEST_URI_PREFIX + state + REQUEST_URI_DELIMITER + expiresAt.toEpochMilli(); + state = state + REQUEST_URI_DELIMITER + expiresAt.toEpochMilli(); + return new OAuth2PushedAuthorizationRequestUri(requestUri, state, expiresAt); } static OAuth2PushedAuthorizationRequestUri parse(String requestUri) { int stateStartIndex = REQUEST_URI_PREFIX.length(); int expiresAtStartIndex = requestUri.indexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length(); - OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri(); - pushedAuthorizationRequestUri.requestUri = requestUri; - pushedAuthorizationRequestUri.state = requestUri.substring(stateStartIndex); - pushedAuthorizationRequestUri.expiresAt = Instant - .ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex))); - return pushedAuthorizationRequestUri; + String state = requestUri.substring(stateStartIndex); + Instant expiresAt = Instant.ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex))); + return new OAuth2PushedAuthorizationRequestUri(requestUri, state, expiresAt); } String getRequestUri() { @@ -81,7 +75,10 @@ final class OAuth2PushedAuthorizationRequestUri { return this.expiresAt; } - private OAuth2PushedAuthorizationRequestUri() { + private OAuth2PushedAuthorizationRequestUri(String requestUri, String state, Instant expiresAt) { + this.requestUri = requestUri; + this.state = state; + this.expiresAt = expiresAt; } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java index c6c50a3215..b7d4cdd1c1 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java @@ -105,6 +105,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -137,6 +138,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic } OAuth2Authorization.Token refreshToken = authorization.getRefreshToken(); + Assert.notNull(refreshToken, "refreshToken cannot be null"); if (!refreshToken.isActive()) { // As per https://tools.ietf.org/html/rfc6749#section-5.2 // invalid_grant: The provided authorization grant (e.g., authorization code, @@ -168,7 +170,10 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic && clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { // For public clients, verify the DPoP Proof public key is same as (current) // access token public key binding - Map accessTokenClaims = authorization.getAccessToken().getClaims(); + OAuth2Authorization.Token accessToken = authorization.getAccessToken(); + Assert.notNull(accessToken, "accessToken cannot be null"); + Map accessTokenClaims = (accessToken.getClaims() != null) ? accessToken.getClaims() + : Collections.emptyMap(); verifyDPoPProofPublicKey(dPoPProof, () -> accessTokenClaims); } @@ -180,10 +185,12 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic scopes = authorizedScopes; } + Authentication principal = authorization.getAttribute(Principal.class.getName()); + Assert.notNull(principal, "principal cannot be null"); // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) - .principal(authorization.getAttribute(Principal.class.getName())) + .principal(principal) .authorizationServerContext(AuthorizationServerContextHolder.getContext()) .authorization(authorization) .authorizedScopes(scopes) diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java index 2cb8e6569b..9acc0bebd4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java @@ -21,7 +21,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java index f2a6967d1c..76b2e5c3a9 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java @@ -48,11 +48,15 @@ public final class OAuth2TokenExchangeActor implements ClaimAccessor { } public String getIssuer() { - return getClaimAsString(OAuth2TokenClaimNames.ISS); + String issuer = getClaimAsString(OAuth2TokenClaimNames.ISS); + Assert.notNull(issuer, "issuer cannot be null"); + return issuer; } public String getSubject() { - return getClaimAsString(OAuth2TokenClaimNames.SUB); + String subject = getClaimAsString(OAuth2TokenClaimNames.SUB); + Assert.notNull(subject, "subject cannot be null"); + return subject; } @Override diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java index 50b9b00930..b0760ad264 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -106,6 +107,7 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(tokenExchangeAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -133,6 +135,7 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti OAuth2Authorization.Token subjectToken = subjectAuthorization .getToken(tokenExchangeAuthentication.getSubjectToken()); + Assert.notNull(subjectToken, "subjectToken cannot be null"); if (!subjectToken.isActive()) { // As per https://tools.ietf.org/html/rfc6749#section-5.2 // invalid_grant: The provided authorization grant (e.g., authorization code, @@ -175,6 +178,7 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti OAuth2Authorization.Token actorToken = actorAuthorization .getToken(tokenExchangeAuthentication.getActorToken()); + Assert.notNull(actorToken, "actorToken cannot be null"); if (!actorToken.isActive()) { // As per https://tools.ietf.org/html/rfc6749#section-5.2 // invalid_grant: The provided authorization grant (e.g., authorization @@ -184,8 +188,11 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); } - if (!isValidTokenType(tokenExchangeAuthentication.getActorTokenType(), actorToken)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); + String actorTokenType = tokenExchangeAuthentication.getActorTokenType(); + if (actorTokenType != null) { + if (!isValidTokenType(actorTokenType, actorToken)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); + } } if (authorizedActorClaims != null) { @@ -288,7 +295,7 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti return new LinkedHashSet<>(requestedScopes); } - private static void validateClaims(Map expectedClaims, Map actualClaims, + private static void validateClaims(Map expectedClaims, @Nullable Map actualClaims, String... claimNames) { if (actualClaims == null) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); @@ -302,8 +309,9 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti } private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization, - OAuth2Authorization actorAuthorization) { + @Nullable OAuth2Authorization actorAuthorization) { Authentication subjectPrincipal = subjectAuthorization.getAttribute(Principal.class.getName()); + Assert.notNull(subjectPrincipal, "subject principal cannot be null"); if (actorAuthorization == null) { if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) { return compositeAuthenticationToken.getSubject(); @@ -312,8 +320,11 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti } // Capture claims for current actor's access token - OAuth2TokenExchangeActor currentActor = new OAuth2TokenExchangeActor( - actorAuthorization.getAccessToken().getClaims()); + OAuth2Authorization.Token actorAccessToken = actorAuthorization.getAccessToken(); + Assert.notNull(actorAccessToken, "actor access token cannot be null"); + Map actorAccessTokenClaims = actorAccessToken.getClaims(); + Assert.notNull(actorAccessTokenClaims, "actor access token claims cannot be null"); + OAuth2TokenExchangeActor currentActor = new OAuth2TokenExchangeActor(actorAccessTokenClaims); List actorPrincipals = new LinkedList<>(); actorPrincipals.add(currentActor); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java index 0c320c56a2..90c9c238f3 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java @@ -22,7 +22,8 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; @@ -43,9 +44,9 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG private final String subjectTokenType; - private final String actorToken; + private final @Nullable String actorToken; - private final String actorTokenType; + private final @Nullable String actorTokenType; private final Set resources; @@ -113,17 +114,17 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG /** * Returns the actor token. - * @return the actor token + * @return the actor token, or {@code null} if not provided */ - public String getActorToken() { + public @Nullable String getActorToken() { return this.actorToken; } /** * Returns the actor token type. - * @return the actor token type + * @return the actor token type, or {@code null} if not provided */ - public String getActorTokenType() { + public @Nullable String getActorTokenType() { return this.actorTokenType; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java index 35ebf79a41..fb8affcb1a 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -51,12 +53,12 @@ public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAut } @Override - public Object getPrincipal() { + public @Nullable Object getPrincipal() { return this.subject.getPrincipal(); } @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java index 544cf3cf53..847e013889 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java @@ -102,6 +102,7 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut OAuth2Authorization.Token authorizedToken = authorization .getToken(tokenIntrospectionAuthentication.getToken()); + Assert.notNull(authorizedToken, "authorizedToken cannot be null"); if (!authorizedToken.isActive()) { if (this.logger.isTraceEnabled()) { this.logger.trace("Did not introspect token since not active"); @@ -112,6 +113,7 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut RegisteredClient authorizedClient = this.registeredClientRepository .findById(authorization.getRegisteredClientId()); + Assert.notNull(authorizedClient, "authorizedClient cannot be null"); OAuth2TokenIntrospection tokenClaims = withActiveTokenClaims(authorizedToken, authorizedClient); if (this.logger.isTraceEnabled()) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java index d79920571f..9580b2e6d4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationToken.java @@ -21,7 +21,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; @@ -46,7 +47,7 @@ public class OAuth2TokenIntrospectionAuthenticationToken extends AbstractAuthent private final Authentication clientPrincipal; - private final String tokenTypeHint; + private final @Nullable String tokenTypeHint; private final Map additionalParameters; @@ -118,8 +119,7 @@ public class OAuth2TokenIntrospectionAuthenticationToken extends AbstractAuthent * Returns the token type hint. * @return the token type hint */ - @Nullable - public String getTokenTypeHint() { + public @Nullable String getTokenTypeHint() { return this.tokenTypeHint; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java index fd3f260ce9..6cd2f7b36f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProvider.java @@ -64,6 +64,7 @@ public final class OAuth2TokenRevocationAuthenticationProvider implements Authen OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils .getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + Assert.notNull(registeredClient, "registeredClient cannot be null"); OAuth2Authorization authorization = this.authorizationService .findByToken(tokenRevocationAuthentication.getToken(), null); @@ -80,6 +81,7 @@ public final class OAuth2TokenRevocationAuthenticationProvider implements Authen } OAuth2Authorization.Token token = authorization.getToken(tokenRevocationAuthentication.getToken()); + Assert.notNull(token, "token cannot be null"); authorization = OAuth2Authorization.from(authorization).invalidate(token.getToken()).build(); this.authorizationService.save(authorization); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java index dc331aafdf..d3e384e571 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationToken.java @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.server.authorization.authentication; import java.io.Serial; import java.util.Collections; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2Token; @@ -43,7 +44,7 @@ public class OAuth2TokenRevocationAuthenticationToken extends AbstractAuthentica private final Authentication clientPrincipal; - private final String tokenTypeHint; + private final @Nullable String tokenTypeHint; /** * Constructs an {@code OAuth2TokenRevocationAuthenticationToken} using the provided @@ -100,8 +101,7 @@ public class OAuth2TokenRevocationAuthenticationToken extends AbstractAuthentica * Returns the token type hint. * @return the token type hint */ - @Nullable - public String getTokenTypeHint() { + public @Nullable String getTokenTypeHint() { return this.tokenTypeHint; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java index 0b486c373d..537d6e0047 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/PublicClientAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -70,7 +71,7 @@ public final class PublicClientAuthenticationProvider implements AuthenticationP } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; if (!ClientAuthenticationMethod.NONE.equals(clientAuthentication.getClientAuthenticationMethod())) { @@ -80,7 +81,7 @@ public final class PublicClientAuthenticationProvider implements AuthenticationP String clientId = clientAuthentication.getPrincipal().toString(); RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); + throw invalidClient(OAuth2ParameterNames.CLIENT_ID); } if (this.logger.isTraceEnabled()) { @@ -89,7 +90,7 @@ public final class PublicClientAuthenticationProvider implements AuthenticationP if (!registeredClient.getClientAuthenticationMethods() .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); + throw invalidClient("authentication_method"); } if (this.logger.isTraceEnabled()) { @@ -112,10 +113,10 @@ public final class PublicClientAuthenticationProvider implements AuthenticationP return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication); } - private static void throwInvalidClient(String parameterName) { + private static OAuth2AuthenticationException invalidClient(String parameterName) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java index 7d95ed092b..cb1cd3fce8 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509ClientCertificateAuthenticationProvider.java @@ -21,6 +21,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -79,7 +80,7 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication; if (!ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) @@ -91,7 +92,7 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen String clientId = clientAuthentication.getPrincipal().toString(); RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); + throw invalidClient(OAuth2ParameterNames.CLIENT_ID); } if (this.logger.isTraceEnabled()) { @@ -100,11 +101,11 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen if (!registeredClient.getClientAuthenticationMethods() .contains(clientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); + throw invalidClient("authentication_method"); } if (!(clientAuthentication.getCredentials() instanceof X509Certificate[])) { - throwInvalidClient("credentials"); + throw invalidClient("credentials"); } OAuth2ClientAuthenticationContext authenticationContext = OAuth2ClientAuthenticationContext @@ -170,22 +171,23 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication(); RegisteredClient registeredClient = clientAuthenticationContext.getRegisteredClient(); X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials(); + Assert.notEmpty(clientCertificateChain, "clientCertificateChain cannot be empty"); X509Certificate clientCertificate = clientCertificateChain[0]; String expectedSubjectDN = registeredClient.getClientSettings().getX509CertificateSubjectDN(); if (!StringUtils.hasText(expectedSubjectDN) || !clientCertificate.getSubjectX500Principal().getName().equals(expectedSubjectDN)) { - throwInvalidClient("x509_certificate_subject_dn"); + throw invalidClient("x509_certificate_subject_dn"); } } - private static void throwInvalidClient(String parameterName) { - throwInvalidClient(parameterName, null); + private static OAuth2AuthenticationException invalidClient(String parameterName) { + return invalidClient(parameterName, null); } - private static void throwInvalidClient(String parameterName, Throwable cause) { + private static OAuth2AuthenticationException invalidClient(String parameterName, @Nullable Throwable cause) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Client authentication failed: " + parameterName, ERROR_URI); - throw new OAuth2AuthenticationException(error, error.toString(), cause); + return new OAuth2AuthenticationException(error, error.toString(), cause); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java index 252b4ac878..61d41349ae 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java @@ -37,6 +37,7 @@ import javax.security.auth.x500.X500Principal; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKMatcher; import com.nimbusds.jose.jwk.JWKSet; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -48,6 +49,7 @@ 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.server.authorization.client.RegisteredClient; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @@ -74,12 +76,13 @@ final class X509SelfSignedCertificateVerifier implements Consumer { @@ -128,7 +131,7 @@ final class X509SelfSignedCertificateVerifier implements Consumer jwkSetSupplier = this.jwkSets.computeIfAbsent(registeredClient.getId(), (key) -> { if (!StringUtils.hasText(registeredClient.getClientSettings().getJwkSetUrl())) { - throwInvalidClient("client_jwk_set_url"); + throw invalidClient("client_jwk_set_url"); } return new JwkSetHolder(registeredClient.getClientSettings().getJwkSetUrl()); }); @@ -136,34 +139,36 @@ final class X509SelfSignedCertificateVerifier implements Consumer request = new RequestEntity<>(headers, HttpMethod.GET, jwkSetUri); - ResponseEntity response = null; + final ResponseEntity response; try { response = this.restOperations.exchange(request, String.class); } catch (Exception ex) { - throwInvalidClient("jwk_set_response_error", ex); + throw invalidClient("jwk_set_response_error", ex); } if (response.getStatusCode().value() != 200) { - throwInvalidClient("jwk_set_response_status"); + throw invalidClient("jwk_set_response_status"); } - JWKSet jwkSet = null; + final JWKSet jwkSet; try { - jwkSet = JWKSet.parse(response.getBody()); + String body = response.getBody(); + Assert.notNull(body, "response body cannot be null"); + jwkSet = JWKSet.parse(body); } catch (ParseException ex) { - throwInvalidClient("jwk_set_response_body", ex); + throw invalidClient("jwk_set_response_body", ex); } return jwkSet; @@ -177,9 +182,9 @@ final class X509SelfSignedCertificateVerifier implements Consumer result = this.jdbcOperations.query(LOAD_REGISTERED_CLIENT_SQL + filter, this.registeredClientRowMapper, args); return !result.isEmpty() ? result.get(0) : null; @@ -334,10 +335,15 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor // @formatter:off RegisteredClient.Builder builder = RegisteredClient.withId(rs.getString("id")) - .clientId(rs.getString("client_id")) - .clientIdIssuedAt((clientIdIssuedAt != null) ? clientIdIssuedAt.toInstant() : null) - .clientSecret(rs.getString("client_secret")) - .clientSecretExpiresAt((clientSecretExpiresAt != null) ? clientSecretExpiresAt.toInstant() : null) + .clientId(rs.getString("client_id")); + if (clientIdIssuedAt != null) { + builder.clientIdIssuedAt(clientIdIssuedAt.toInstant()); + } + builder.clientSecret(rs.getString("client_secret")); + if (clientSecretExpiresAt != null) { + builder.clientSecretExpiresAt(clientSecretExpiresAt.toInstant()); + } + builder .clientName(rs.getString("client_name")) .clientAuthenticationMethods((authenticationMethods) -> clientAuthenticationMethods.forEach((authenticationMethod) -> @@ -558,7 +564,7 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources() .registerResource(new ClassPathResource( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java index 7edab94097..5367cd1abb 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClient.java @@ -27,7 +27,8 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; @@ -50,31 +51,31 @@ public class RegisteredClient implements Serializable { @Serial private static final long serialVersionUID = -717282636175335081L; - private String id; + private @Nullable String id; - private String clientId; + private @Nullable String clientId; - private Instant clientIdIssuedAt; + private @Nullable Instant clientIdIssuedAt; - private String clientSecret; + private @Nullable String clientSecret; - private Instant clientSecretExpiresAt; + private @Nullable Instant clientSecretExpiresAt; - private String clientName; + private @Nullable String clientName; - private Set clientAuthenticationMethods; + private @Nullable Set clientAuthenticationMethods; - private Set authorizationGrantTypes; + private @Nullable Set authorizationGrantTypes; - private Set redirectUris; + private @Nullable Set redirectUris; - private Set postLogoutRedirectUris; + private @Nullable Set postLogoutRedirectUris; - private Set scopes; + private @Nullable Set scopes; - private ClientSettings clientSettings; + private @Nullable ClientSettings clientSettings; - private TokenSettings tokenSettings; + private @Nullable TokenSettings tokenSettings; protected RegisteredClient() { } @@ -84,6 +85,7 @@ public class RegisteredClient implements Serializable { * @return the identifier for the registration */ public String getId() { + Assert.notNull(this.id, "id cannot be null"); return this.id; } @@ -92,6 +94,7 @@ public class RegisteredClient implements Serializable { * @return the client identifier */ public String getClientId() { + Assert.notNull(this.clientId, "clientId cannot be null"); return this.clientId; } @@ -99,8 +102,7 @@ public class RegisteredClient implements Serializable { * Returns the time at which the client identifier was issued. * @return the time at which the client identifier was issued */ - @Nullable - public Instant getClientIdIssuedAt() { + public @Nullable Instant getClientIdIssuedAt() { return this.clientIdIssuedAt; } @@ -108,8 +110,7 @@ public class RegisteredClient implements Serializable { * Returns the client secret or {@code null} if not available. * @return the client secret or {@code null} if not available */ - @Nullable - public String getClientSecret() { + public @Nullable String getClientSecret() { return this.clientSecret; } @@ -119,8 +120,7 @@ public class RegisteredClient implements Serializable { * @return the time at which the client secret expires or {@code null} if it does not * expire */ - @Nullable - public Instant getClientSecretExpiresAt() { + public @Nullable Instant getClientSecretExpiresAt() { return this.clientSecretExpiresAt; } @@ -129,6 +129,7 @@ public class RegisteredClient implements Serializable { * @return the client name */ public String getClientName() { + Assert.notNull(this.clientName, "clientName cannot be null"); return this.clientName; } @@ -139,6 +140,7 @@ public class RegisteredClient implements Serializable { * method(s)} */ public Set getClientAuthenticationMethods() { + Assert.notNull(this.clientAuthenticationMethods, "clientAuthenticationMethods cannot be null"); return this.clientAuthenticationMethods; } @@ -149,6 +151,7 @@ public class RegisteredClient implements Serializable { * type(s)} */ public Set getAuthorizationGrantTypes() { + Assert.notNull(this.authorizationGrantTypes, "authorizationGrantTypes cannot be null"); return this.authorizationGrantTypes; } @@ -157,6 +160,7 @@ public class RegisteredClient implements Serializable { * @return the {@code Set} of redirect URI(s) */ public Set getRedirectUris() { + Assert.notNull(this.redirectUris, "redirectUris cannot be null"); return this.redirectUris; } @@ -167,6 +171,7 @@ public class RegisteredClient implements Serializable { * @return the {@code Set} of post logout redirect URI(s) */ public Set getPostLogoutRedirectUris() { + Assert.notNull(this.postLogoutRedirectUris, "postLogoutRedirectUris cannot be null"); return this.postLogoutRedirectUris; } @@ -175,6 +180,7 @@ public class RegisteredClient implements Serializable { * @return the {@code Set} of scope(s) */ public Set getScopes() { + Assert.notNull(this.scopes, "scopes cannot be null"); return this.scopes; } @@ -183,6 +189,7 @@ public class RegisteredClient implements Serializable { * @return the {@link ClientSettings} */ public ClientSettings getClientSettings() { + Assert.notNull(this.clientSettings, "clientSettings cannot be null"); return this.clientSettings; } @@ -191,6 +198,7 @@ public class RegisteredClient implements Serializable { * @return the {@link TokenSettings} */ public TokenSettings getTokenSettings() { + Assert.notNull(this.tokenSettings, "tokenSettings cannot be null"); return this.tokenSettings; } @@ -261,17 +269,17 @@ public class RegisteredClient implements Serializable { */ public static class Builder { - private String id; + private @Nullable String id; - private String clientId; + private @Nullable String clientId; - private Instant clientIdIssuedAt; + private @Nullable Instant clientIdIssuedAt; - private String clientSecret; + private @Nullable String clientSecret; - private Instant clientSecretExpiresAt; + private @Nullable Instant clientSecretExpiresAt; - private String clientName; + private @Nullable String clientName; private final Set clientAuthenticationMethods = new HashSet<>(); @@ -283,9 +291,9 @@ public class RegisteredClient implements Serializable { private final Set scopes = new HashSet<>(); - private ClientSettings clientSettings; + private @Nullable ClientSettings clientSettings; - private TokenSettings tokenSettings; + private @Nullable TokenSettings tokenSettings; protected Builder(String id) { this.id = id; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java index 450a4bca59..53ead65b03 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/RegisteredClientRepository.java @@ -16,7 +16,7 @@ package org.springframework.security.oauth2.server.authorization.client; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * A repository for OAuth 2.0 {@link RegisteredClient}(s). @@ -45,8 +45,7 @@ public interface RegisteredClientRepository { * @param id the registration identifier * @return the {@link RegisteredClient} if found, otherwise {@code null} */ - @Nullable - RegisteredClient findById(String id); + @Nullable RegisteredClient findById(String id); /** * Returns the registered client identified by the provided {@code clientId}, or @@ -54,7 +53,6 @@ public interface RegisteredClientRepository { * @param clientId the client identifier * @return the {@link RegisteredClient} if found, otherwise {@code null} */ - @Nullable - RegisteredClient findByClientId(String clientId); + @Nullable RegisteredClient findByClientId(String clientId); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/package-info.java new file mode 100644 index 0000000000..5db104529f --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Client registration persistence for the authorization server, including + * {@link org.springframework.security.oauth2.server.authorization.client.RegisteredClient} + * and repository abstractions. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.client; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java index b9dbbee9c5..db079ab3f5 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/Context.java @@ -16,7 +16,8 @@ package org.springframework.security.oauth2.server.authorization.context; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -34,8 +35,7 @@ public interface Context { * @return the value of the attribute associated to the key, or {@code null} if not * available */ - @Nullable - V get(Object key); + @Nullable V get(Object key); /** * Returns the value of the attribute associated to the key. @@ -44,8 +44,7 @@ public interface Context { * @return the value of the attribute associated to the key, or {@code null} if not * available or not of the specified type */ - @Nullable - default V get(Class key) { + default @Nullable V get(Class key) { Assert.notNull(key, "key cannot be null"); V value = get((Object) key); return key.isInstance(value) ? value : null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/package-info.java new file mode 100644 index 0000000000..0dd7777ad2 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Context types that carry authorization server request state and attributes during + * protocol processing. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.context; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java index 11595e4708..a596505184 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/OAuth2ClientRegistrationRegisteredClientConverter.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.converter; import java.time.Instant; import java.util.Base64; +import java.util.List; import java.util.UUID; import java.util.function.Consumer; @@ -58,9 +59,11 @@ public final class OAuth2ClientRegistrationRegisteredClientConverter // @formatter:off RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId(CLIENT_ID_GENERATOR.generateKey()) - .clientIdIssuedAt(Instant.now()) - .clientName(clientRegistration.getClientName()); - + .clientIdIssuedAt(Instant.now()); + String clientName = clientRegistration.getClientName(); + if (clientName != null) { + builder.clientName(clientName); + } if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { builder .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) @@ -80,9 +83,10 @@ public final class OAuth2ClientRegistrationRegisteredClientConverter redirectUris.addAll(clientRegistration.getRedirectUris())); } - if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) { + List grantTypes = clientRegistration.getGrantTypes(); + if (!CollectionUtils.isEmpty(grantTypes)) { builder.authorizationGrantTypes((authorizationGrantTypes) -> - clientRegistration.getGrantTypes().forEach((grantType) -> + grantTypes.forEach((grantType) -> authorizationGrantTypes.add(new AuthorizationGrantType(grantType)))); } else { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/RegisteredClientOAuth2ClientRegistrationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/RegisteredClientOAuth2ClientRegistrationConverter.java index 7339dfcf10..cc9b5ba244 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/RegisteredClientOAuth2ClientRegistrationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/RegisteredClientOAuth2ClientRegistrationConverter.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.converter; +import java.time.Instant; + import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; @@ -39,8 +41,11 @@ public final class RegisteredClientOAuth2ClientRegistrationConverter // @formatter:off OAuth2ClientRegistration.Builder builder = OAuth2ClientRegistration.builder() .clientId(registeredClient.getClientId()) - .clientIdIssuedAt(registeredClient.getClientIdIssuedAt()) .clientName(registeredClient.getClientName()); + Instant clientIdIssuedAt = registeredClient.getClientIdIssuedAt(); + if (clientIdIssuedAt != null) { + builder.clientIdIssuedAt(clientIdIssuedAt); + } builder .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/package-info.java new file mode 100644 index 0000000000..66a8d7aca1 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/converter/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * {@link org.springframework.core.convert.converter.Converter} implementations for + * authorization server domain types. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.converter; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java index 623099f7f9..22f3deae58 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.http.converter; +import org.jspecify.annotations.Nullable; + import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; @@ -54,7 +56,7 @@ final class HttpMessageConverters { } @SuppressWarnings("removal") - static GenericHttpMessageConverter getJsonMessageConverter() { + static @Nullable GenericHttpMessageConverter getJsonMessageConverter() { if (jacksonPresent) { return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter()); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java index 9a9a660870..0be1cceccd 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java @@ -53,8 +53,7 @@ public class OAuth2AuthorizationServerMetadataHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter(); @@ -62,6 +61,9 @@ public class OAuth2AuthorizationServerMetadataHttpMessageConverter public OAuth2AuthorizationServerMetadataHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java index d9b2777152..f000d8a422 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java @@ -26,6 +26,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; @@ -60,8 +62,7 @@ public class OAuth2ClientRegistrationHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OAuth2ClientRegistration> clientRegistrationConverter = new MapOAuth2ClientRegistrationConverter(); @@ -69,6 +70,9 @@ public class OAuth2ClientRegistrationHttpMessageConverter public OAuth2ClientRegistrationHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override @@ -187,7 +191,7 @@ public class OAuth2ClientRegistrationHttpMessageConverter return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); } - private static Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) { + private static @Nullable Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) { if (clientSecretExpiresAt != null && String.valueOf(clientSecretExpiresAt).equals("0")) { // 0 indicates that client_secret_expires_at does not expire return null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java index 9f553475c2..6461e515ce 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java @@ -61,8 +61,7 @@ public class OAuth2TokenIntrospectionHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OAuth2TokenIntrospection> tokenIntrospectionConverter = new MapOAuth2TokenIntrospectionConverter(); @@ -70,6 +69,9 @@ public class OAuth2TokenIntrospectionHttpMessageConverter public OAuth2TokenIntrospectionHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/package-info.java new file mode 100644 index 0000000000..4227730f2c --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * HTTP message converters for OAuth2 Authorization Server protocol representations. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.http.converter; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/JsonNodeUtils.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/JsonNodeUtils.java index 84301d1e01..3d97f4961b 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/JsonNodeUtils.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/JsonNodeUtils.java @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.server.authorization.jackson; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; import tools.jackson.core.type.TypeReference; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.JsonNode; @@ -37,7 +38,7 @@ abstract class JsonNodeUtils { static final TypeReference> STRING_OBJECT_MAP = new TypeReference<>() { }; - static String findStringValue(JsonNode jsonNode, String fieldName) { + static @Nullable String findStringValue(@Nullable JsonNode jsonNode, String fieldName) { if (jsonNode == null) { return null; } @@ -45,7 +46,7 @@ abstract class JsonNodeUtils { return (value != null && value.isString()) ? value.stringValue() : null; } - static T findValue(JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, + static @Nullable T findValue(@Nullable JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, DeserializationContext context) { if (jsonNode == null) { return null; @@ -55,7 +56,7 @@ abstract class JsonNodeUtils { ? context.readTreeAsValue(value, context.getTypeFactory().constructType(valueTypeReference)) : null; } - static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) { + static @Nullable JsonNode findObjectNode(@Nullable JsonNode jsonNode, String fieldName) { if (jsonNode == null) { return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/OAuth2AuthorizationRequestDeserializer.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/OAuth2AuthorizationRequestDeserializer.java index ce1e55df80..c4fbcb3771 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/OAuth2AuthorizationRequestDeserializer.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson/OAuth2AuthorizationRequestDeserializer.java @@ -16,6 +16,10 @@ package org.springframework.security.oauth2.server.authorization.jackson; +import java.util.Collections; +import java.util.Map; + +import org.jspecify.annotations.Nullable; import tools.jackson.core.JsonParser; import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.JsonNode; @@ -25,6 +29,7 @@ import tools.jackson.databind.exc.InvalidFormatException; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder; +import org.springframework.util.Assert; /** * A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}. @@ -45,16 +50,27 @@ final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer additionalParameters = JsonNodeUtils.findValue(root, "additionalParameters", + JsonNodeUtils.STRING_OBJECT_MAP, context); + builder.additionalParameters((additionalParameters != null) ? additionalParameters : Collections.emptyMap()); + String authorizationRequestUri = JsonNodeUtils.findStringValue(root, "authorizationRequestUri"); + if (authorizationRequestUri != null) { + builder.authorizationRequestUri(authorizationRequestUri); + } + Map attributes = JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, + context); + builder.attributes((attributes != null) ? attributes : Collections.emptyMap()); return builder.build(); } @@ -66,7 +82,10 @@ final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer> STRING_OBJECT_MAP = new TypeReference<>() { }; - static String findStringValue(JsonNode jsonNode, String fieldName) { + static @Nullable String findStringValue(@Nullable JsonNode jsonNode, String fieldName) { if (jsonNode == null) { return null; } @@ -49,7 +50,7 @@ abstract class JsonNodeUtils { return (value != null && value.isTextual()) ? value.asText() : null; } - static T findValue(JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, + static @Nullable T findValue(@Nullable JsonNode jsonNode, String fieldName, TypeReference valueTypeReference, ObjectMapper mapper) { if (jsonNode == null) { return null; @@ -58,7 +59,7 @@ abstract class JsonNodeUtils { return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null; } - static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) { + static @Nullable JsonNode findObjectNode(@Nullable JsonNode jsonNode, String fieldName) { if (jsonNode == null) { return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java index 3bad21fe2c..0dadbae1b2 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.jackson2; import java.io.IOException; +import java.util.Map; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; @@ -24,10 +25,12 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jspecify.annotations.Nullable; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder; +import org.springframework.util.Assert; /** * A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}. @@ -57,27 +60,42 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer additionalParameters = JsonNodeUtils.findValue(root, "additionalParameters", + JsonNodeUtils.STRING_OBJECT_MAP, mapper); + if (additionalParameters != null) { + builder.additionalParameters(additionalParameters); + } + String authorizationRequestUri = JsonNodeUtils.findStringValue(root, "authorizationRequestUri"); + if (authorizationRequestUri != null) { + builder.authorizationRequestUri(authorizationRequestUri); + } + Map attributes = JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, + mapper); + if (attributes != null) { + builder.attributes(attributes); + } return builder.build(); } - private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) + private Builder getBuilder(JsonParser parser, @Nullable AuthorizationGrantType authorizationGrantType) throws JsonParseException { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { + if (authorizationGrantType != null + && authorizationGrantType.equals(AuthorizationGrantType.AUTHORIZATION_CODE)) { return OAuth2AuthorizationRequest.authorizationCode(); } throw new JsonParseException(parser, "Invalid authorizationGrantType"); } - private static AuthorizationGrantType convertAuthorizationGrantType(JsonNode jsonNode) { + private static @Nullable AuthorizationGrantType convertAuthorizationGrantType(@Nullable JsonNode jsonNode) { String value = JsonNodeUtils.findStringValue(jsonNode, "value"); if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) { return AuthorizationGrantType.AUTHORIZATION_CODE; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/package-info.java new file mode 100644 index 0000000000..3523562bef --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Jackson 2 ({@code com.fasterxml.jackson}) serialization support for authorization + * server types (deprecated in favor of {@code jackson}). + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.jackson2; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java index 9fad4443d4..a045577c75 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcClientMetadataClaimAccessor.java @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.authorization.oidc; import java.net.URL; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcIdToken; @@ -53,7 +55,7 @@ public interface OidcClientMetadataClaimAccessor extends OAuth2ClientMetadataCla * to after a logout has been performed. * @return the post logout redirection {@code URI} values used by the Client */ - default List getPostLogoutRedirectUris() { + default @Nullable List getPostLogoutRedirectUris() { return getClaimAsStringList(OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); } @@ -66,7 +68,7 @@ public interface OidcClientMetadataClaimAccessor extends OAuth2ClientMetadataCla * @return the {@link JwsAlgorithm JWS} algorithm that must be used for signing the * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint */ - default String getTokenEndpointAuthenticationSigningAlgorithm() { + default @Nullable String getTokenEndpointAuthenticationSigningAlgorithm() { return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG); } @@ -77,7 +79,7 @@ public interface OidcClientMetadataClaimAccessor extends OAuth2ClientMetadataCla * @return the {@link SignatureAlgorithm JWS} algorithm required for signing the * {@link OidcIdToken ID Token} issued to the Client */ - default String getIdTokenSignedResponseAlgorithm() { + default @Nullable String getIdTokenSignedResponseAlgorithm() { return getClaimAsString(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG); } @@ -87,7 +89,7 @@ public interface OidcClientMetadataClaimAccessor extends OAuth2ClientMetadataCla * @return the Registration Access Token that can be used at the Client Configuration * Endpoint */ - default String getRegistrationAccessToken() { + default @Nullable String getRegistrationAccessToken() { return getClaimAsString(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN); } @@ -97,7 +99,7 @@ public interface OidcClientMetadataClaimAccessor extends OAuth2ClientMetadataCla * @return the {@code URL} of the Client Configuration Endpoint where the Registration * Access Token can be used */ - default URL getRegistrationClientUrl() { + default @Nullable URL getRegistrationClientUrl() { return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java index e8cc668c93..cc2d489c18 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/OidcProviderMetadataClaimAccessor.java @@ -19,11 +19,14 @@ package org.springframework.security.oauth2.server.authorization.oidc; import java.net.URL; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimAccessor; +import org.springframework.util.Assert; /** * A {@link ClaimAccessor} for the "claims" that can be returned in the OpenID Provider @@ -47,7 +50,9 @@ public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationSe * @return the Subject Identifier types supported */ default List getSubjectTypes() { - return getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED); + List subjectTypes = getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED); + Assert.notNull(subjectTypes, "subjectTypes cannot be null"); + return subjectTypes; } /** @@ -58,7 +63,10 @@ public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationSe * {@link OidcIdToken ID Token} */ default List getIdTokenSigningAlgorithms() { - return getClaimAsStringList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); + List idTokenSigningAlgorithms = getClaimAsStringList( + OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); + Assert.notNull(idTokenSigningAlgorithms, "idTokenSigningAlgorithms cannot be null"); + return idTokenSigningAlgorithms; } /** @@ -66,7 +74,7 @@ public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationSe * {@code (userinfo_endpoint)}. * @return the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint */ - default URL getUserInfoEndpoint() { + default @Nullable URL getUserInfoEndpoint() { return getClaimAsURL(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT); } @@ -76,7 +84,9 @@ public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationSe * @return the {@code URL} of the OpenID Connect 1.0 End Session Endpoint */ default URL getEndSessionEndpoint() { - return getClaimAsURL(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT); + URL endSessionEndpoint = getClaimAsURL(OidcProviderMetadataClaimNames.END_SESSION_ENDPOINT); + Assert.notNull(endSessionEndpoint, "endSessionEndpoint cannot be null"); + return endSessionEndpoint; } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java index 588a1e784a..aa5fe23cda 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientConfigurationAuthenticationProvider.java @@ -18,10 +18,12 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AuthenticationProvider; @@ -99,7 +101,7 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = (OidcClientRegistrationAuthenticationToken) authentication; if (!StringUtils.hasText(clientRegistrationAuthentication.getClientId())) { @@ -132,6 +134,7 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth } OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); + Assert.notNull(authorizedAccessToken, "authorizedAccessToken cannot be null"); if (!authorizedAccessToken.isActive()) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); } @@ -149,8 +152,9 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, OAuth2Authorization authorization) { - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationAuthentication.getClientId()); + String clientId = clientRegistrationAuthentication.getClientId(); + Assert.hasText(clientId, "clientId cannot be empty"); + RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); } @@ -176,9 +180,11 @@ public final class OidcClientConfigurationAuthenticationProvider implements Auth @SuppressWarnings("unchecked") private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) { + Map claims = authorizedAccessToken.getClaims(); + Assert.notNull(claims, "claims cannot be null"); Collection authorizedScope = Collections.emptySet(); - if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { - authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); + if (claims.containsKey(OAuth2ParameterNames.SCOPE)) { + authorizedScope = (Collection) claims.get(OAuth2ParameterNames.SCOPE); } if (!authorizedScope.containsAll(requiredScope)) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java index b402f306d7..1d670fc404 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AuthenticationProvider; @@ -124,7 +125,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = (OidcClientRegistrationAuthenticationToken) authentication; if (clientRegistrationAuthentication.getClientRegistration() == null) { @@ -157,6 +158,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe } OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); + Assert.notNull(authorizedAccessToken, "authorizedAccessToken cannot be null"); if (!authorizedAccessToken.isActive()) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); } @@ -210,18 +212,24 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, OAuth2Authorization authorization) { - if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) { + OidcClientRegistration clientRegistrationRequest = clientRegistrationAuthentication.getClientRegistration(); + Assert.notNull(clientRegistrationRequest, "clientRegistration cannot be null"); + + List redirectUris = (clientRegistrationRequest.getRedirectUris() != null) + ? clientRegistrationRequest.getRedirectUris() : Collections.emptyList(); + if (!isValidRedirectUris(redirectUris)) { throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI, OidcClientMetadataClaimNames.REDIRECT_URIS); } - if (!isValidRedirectUris( - clientRegistrationAuthentication.getClientRegistration().getPostLogoutRedirectUris())) { + List postLogoutRedirectUris = (clientRegistrationRequest.getPostLogoutRedirectUris() != null) + ? clientRegistrationRequest.getPostLogoutRedirectUris() : Collections.emptyList(); + if (!isValidRedirectUris(postLogoutRedirectUris)) { throwInvalidClientRegistration("invalid_client_metadata", OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS); } - if (!isValidTokenEndpointAuthenticationMethod(clientRegistrationAuthentication.getClientRegistration())) { + if (!isValidTokenEndpointAuthenticationMethod(clientRegistrationRequest)) { throwInvalidClientRegistration("invalid_client_metadata", OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD); } @@ -230,8 +238,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe this.logger.trace("Validated client registration request parameters"); } - RegisteredClient registeredClient = this.registeredClientConverter - .convert(clientRegistrationAuthentication.getClientRegistration()); + RegisteredClient registeredClient = this.registeredClientConverter.convert(clientRegistrationRequest); if (StringUtils.hasText(registeredClient.getClientSecret())) { // Encode the client secret @@ -240,8 +247,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe .build(); this.registeredClientRepository.save(updatedRegisteredClient); if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue() - .equals(clientRegistrationAuthentication.getClientRegistration() - .getTokenEndpointAuthenticationMethod())) { + .equals(clientRegistrationRequest.getTokenEndpointAuthenticationMethod())) { // gh-1344 Return the hashed client_secret registeredClient = updatedRegisteredClient; } @@ -257,8 +263,10 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient); // Invalidate the "initial" access token as it can only be used once + OAuth2Authorization.Token initialAccessToken = authorization.getAccessToken(); + Assert.notNull(initialAccessToken, "initialAccessToken cannot be null"); OAuth2Authorization.Builder builder = OAuth2Authorization.from(authorization) - .invalidate(authorization.getAccessToken().getToken()); + .invalidate(initialAccessToken.getToken()); if (authorization.getRefreshToken() != null) { builder.invalidate(authorization.getRefreshToken().getToken()); } @@ -271,8 +279,11 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe Map clientRegistrationClaims = this.clientRegistrationConverter.convert(registeredClient) .getClaims(); + OAuth2Authorization.Token registrationAccessToken = registeredClientAuthorization + .getAccessToken(); + Assert.notNull(registrationAccessToken, "registrationAccessToken cannot be null"); OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(clientRegistrationClaims) - .registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue()) + .registrationAccessToken(registrationAccessToken.getToken().getTokenValue()) .build(); if (this.logger.isTraceEnabled()) { @@ -338,9 +349,11 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe @SuppressWarnings("unchecked") private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) { + Map claims = authorizedAccessToken.getClaims(); + Assert.notNull(claims, "claims cannot be null"); Collection authorizedScope = Collections.emptySet(); - if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { - authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); + if (claims.containsKey(OAuth2ParameterNames.SCOPE)) { + authorizedScope = (Collection) claims.get(OAuth2ParameterNames.SCOPE); } if (!authorizedScope.containsAll(requiredScope)) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java index f894ee151e..d7930f9071 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica import java.io.Serial; import java.util.Collections; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; @@ -44,9 +45,9 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic private final Authentication principal; - private final OidcClientRegistration clientRegistration; + private final @Nullable OidcClientRegistration clientRegistration; - private final String clientId; + private final @Nullable String clientId; /** * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided @@ -95,7 +96,7 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic * Returns the client registration. * @return the client registration */ - public OidcClientRegistration getClientRegistration() { + public @Nullable OidcClientRegistration getClientRegistration() { return this.clientRegistration; } @@ -103,8 +104,7 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic * Returns the client identifier. * @return the client identifier */ - @Nullable - public String getClientId() { + public @Nullable String getClientId() { return this.clientId; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java index e8e2c4460d..6923041399 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationContext; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.util.Assert; @@ -46,9 +47,8 @@ public final class OidcLogoutAuthenticationContext implements OAuth2Authenticati } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -63,7 +63,9 @@ public final class OidcLogoutAuthenticationContext implements OAuth2Authenticati * @return the {@link RegisteredClient} */ public RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java index 5d26b2721c..c9da1f7790 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationProvider.java @@ -26,6 +26,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -99,7 +100,7 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro OAuth2Authorization authorization = this.authorizationService .findByToken(oidcLogoutAuthentication.getIdTokenHint(), ID_TOKEN_TOKEN_TYPE); if (authorization == null) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); + throw createException(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); } if (this.logger.isTraceEnabled()) { @@ -107,13 +108,15 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro } OAuth2Authorization.Token authorizedIdToken = authorization.getToken(OidcIdToken.class); + Assert.notNull(authorizedIdToken, "authorizedIdToken cannot be null"); if (authorizedIdToken.isInvalidated() || authorizedIdToken.isBeforeUse()) { // Expired ID Token should be accepted - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); + throw createException(OAuth2ErrorCodes.INVALID_TOKEN, "id_token_hint"); } RegisteredClient registeredClient = this.registeredClientRepository .findById(authorization.getRegisteredClientId()); + Assert.notNull(registeredClient, "registeredClient cannot be null"); if (this.logger.isTraceEnabled()) { this.logger.trace("Retrieved registered client"); @@ -124,11 +127,11 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro // Validate client identity List audClaim = idToken.getAudience(); if (CollectionUtils.isEmpty(audClaim) || !audClaim.contains(registeredClient.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.AUD); + throw createException(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.AUD); } if (StringUtils.hasText(oidcLogoutAuthentication.getClientId()) && !oidcLogoutAuthentication.getClientId().equals(registeredClient.getClientId())) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); } OidcLogoutAuthenticationContext context = OidcLogoutAuthenticationContext.with(oidcLogoutAuthentication) @@ -144,9 +147,10 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro if (oidcLogoutAuthentication.isPrincipalAuthenticated()) { Authentication currentUserPrincipal = (Authentication) oidcLogoutAuthentication.getPrincipal(); Authentication authorizedUserPrincipal = authorization.getAttribute(Principal.class.getName()); + Assert.notNull(authorizedUserPrincipal, "authorizedUserPrincipal cannot be null"); if (!StringUtils.hasText(idToken.getSubject()) || !currentUserPrincipal.getName().equals(authorizedUserPrincipal.getName())) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.SUB); + throw createException(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.SUB); } // Check for active session @@ -166,7 +170,7 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro String sidClaim = idToken.getClaim("sid"); if (!StringUtils.hasText(sidClaim) || !sidClaim.equals(sessionIdHash)) { - throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid"); + throw createException(OAuth2ErrorCodes.INVALID_TOKEN, "sid"); } } } @@ -205,8 +209,10 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro this.authenticationValidator = authenticationValidator; } - private SessionInformation findSessionInformation(Authentication principal, String sessionId) { - List sessions = this.sessionRegistry.getAllSessions(principal.getPrincipal(), true); + private @Nullable SessionInformation findSessionInformation(Authentication principal, String sessionId) { + Object sessionPrincipal = principal.getPrincipal(); + Assert.notNull(sessionPrincipal, "sessionPrincipal cannot be null"); + List sessions = this.sessionRegistry.getAllSessions(sessionPrincipal, true); SessionInformation sessionInformation = null; if (!CollectionUtils.isEmpty(sessions)) { for (SessionInformation session : sessions) { @@ -219,10 +225,10 @@ public final class OidcLogoutAuthenticationProvider implements AuthenticationPro return sessionInformation; } - private static void throwError(String errorCode, String parameterName) { + private static OAuth2AuthenticationException createException(String errorCode, String parameterName) { OAuth2Error error = new OAuth2Error(errorCode, "OpenID Connect 1.0 Logout Request Parameter: " + parameterName, "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } private static String createHash(String value) throws NoSuchAlgorithmException { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java index 07b82a00a0..cb087d0691 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcLogoutAuthenticationToken.java @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica import java.io.Serial; import java.util.Collections; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -42,17 +43,17 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { private final String idTokenHint; - private final OidcIdToken idToken; + private final @Nullable OidcIdToken idToken; private final Authentication principal; - private final String sessionId; + private final @Nullable String sessionId; - private final String clientId; + private final @Nullable String clientId; - private final String postLogoutRedirectUri; + private final @Nullable String postLogoutRedirectUri; - private final String state; + private final @Nullable String state; /** * Constructs an {@code OidcLogoutAuthenticationToken} using the provided parameters. @@ -147,8 +148,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { * Returns the ID Token previously issued by the Provider to the Client. * @return the ID Token previously issued by the Provider to the Client */ - @Nullable - public OidcIdToken getIdToken() { + public @Nullable OidcIdToken getIdToken() { return this.idToken; } @@ -156,8 +156,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { * Returns the End-User's current authenticated session identifier with the Provider. * @return the End-User's current authenticated session identifier with the Provider */ - @Nullable - public String getSessionId() { + public @Nullable String getSessionId() { return this.sessionId; } @@ -165,8 +164,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { * Returns the client identifier the ID Token was issued to. * @return the client identifier */ - @Nullable - public String getClientId() { + public @Nullable String getClientId() { return this.clientId; } @@ -176,8 +174,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { * @return the URI which the Client is requesting that the End-User's User Agent be * redirected to after a logout has been performed */ - @Nullable - public String getPostLogoutRedirectUri() { + public @Nullable String getPostLogoutRedirectUri() { return this.postLogoutRedirectUri; } @@ -187,8 +184,7 @@ public class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken { * @return the opaque value used by the Client to maintain state between the logout * request and the callback to the {@link #getPostLogoutRedirectUri()} */ - @Nullable - public String getState() { + public @Nullable String getState() { return this.state; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java index 27382ec49a..5f31480d7a 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationContext.java @@ -21,7 +21,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; @@ -48,9 +49,8 @@ public final class OidcUserInfoAuthenticationContext implements OAuth2Authentica } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -65,7 +65,9 @@ public final class OidcUserInfoAuthenticationContext implements OAuth2Authentica * @return the {@link OAuth2AccessToken} */ public OAuth2AccessToken getAccessToken() { - return get(OAuth2AccessToken.class); + OAuth2AccessToken accessToken = get(OAuth2AccessToken.class); + Assert.notNull(accessToken, "accessToken cannot be null"); + return accessToken; } /** @@ -73,7 +75,9 @@ public final class OidcUserInfoAuthenticationContext implements OAuth2Authentica * @return the {@link OAuth2Authorization} */ public OAuth2Authorization getAuthorization() { - return get(OAuth2Authorization.class); + OAuth2Authorization authorization = get(OAuth2Authorization.class); + Assert.notNull(authorization, "authorization cannot be null"); + return authorization; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java index e7b96a3b58..e1f78d5fc4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationProvider.java @@ -98,6 +98,7 @@ public final class OidcUserInfoAuthenticationProvider implements AuthenticationP } OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); + Assert.notNull(authorizedAccessToken, "authorizedAccessToken cannot be null"); if (!authorizedAccessToken.isActive()) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); } @@ -191,7 +192,9 @@ public final class OidcUserInfoAuthenticationProvider implements AuthenticationP @Override public OidcUserInfo apply(OidcUserInfoAuthenticationContext authenticationContext) { OAuth2Authorization authorization = authenticationContext.getAuthorization(); - OidcIdToken idToken = authorization.getToken(OidcIdToken.class).getToken(); + OAuth2Authorization.Token authorizedIdToken = authorization.getToken(OidcIdToken.class); + Assert.notNull(authorizedIdToken, "authorizedIdToken cannot be null"); + OidcIdToken idToken = authorizedIdToken.getToken(); OAuth2AccessToken accessToken = authenticationContext.getAccessToken(); Map scopeRequestedClaims = getClaimsRequestedByScope(idToken.getClaims(), accessToken.getScopes()); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java index a6f7ce07ab..e0935e5f34 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcUserInfoAuthenticationToken.java @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica import java.io.Serial; import java.util.Collections; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; @@ -40,7 +42,7 @@ public class OidcUserInfoAuthenticationToken extends AbstractAuthenticationToken private final Authentication principal; - private final OidcUserInfo userInfo; + private final @Nullable OidcUserInfo userInfo; /** * Constructs an {@code OidcUserInfoAuthenticationToken} using the provided @@ -82,9 +84,9 @@ public class OidcUserInfoAuthenticationToken extends AbstractAuthenticationToken /** * Returns the UserInfo claims. - * @return the UserInfo claims + * @return the UserInfo claims, or {@code null} if not provided */ - public OidcUserInfo getUserInfo() { + public @Nullable OidcUserInfo getUserInfo() { return this.userInfo; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/package-info.java new file mode 100644 index 0000000000..7d9a21ebc5 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * {@link org.springframework.security.authentication.AuthenticationProvider} + * implementations and related types for OpenID Connect 1.0 flows handled by the + * authorization server. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java index e885ea210a..fd763c43ad 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/OidcClientRegistrationRegisteredClientConverter.java @@ -16,8 +16,10 @@ package org.springframework.security.oauth2.server.authorization.oidc.converter; +import java.net.URL; import java.time.Instant; import java.util.Base64; +import java.util.Collection; import java.util.UUID; import org.springframework.core.convert.converter.Converter; @@ -32,6 +34,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** @@ -56,8 +59,11 @@ public final class OidcClientRegistrationRegisteredClientConverter // @formatter:off RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId(CLIENT_ID_GENERATOR.generateKey()) - .clientIdIssuedAt(Instant.now()) - .clientName(clientRegistration.getClientName()); + .clientIdIssuedAt(Instant.now()); + String clientName = clientRegistration.getClientName(); + if (clientName != null) { + builder.clientName(clientName); + } if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { builder @@ -86,9 +92,10 @@ public final class OidcClientRegistrationRegisteredClientConverter postLogoutRedirectUris.addAll(clientRegistration.getPostLogoutRedirectUris())); } - if (!CollectionUtils.isEmpty(clientRegistration.getGrantTypes())) { + Collection grantTypes = clientRegistration.getGrantTypes(); + if (!CollectionUtils.isEmpty(grantTypes)) { builder.authorizationGrantTypes((authorizationGrantTypes) -> - clientRegistration.getGrantTypes().forEach((grantType) -> + grantTypes.forEach((grantType) -> authorizationGrantTypes.add(new AuthorizationGrantType(grantType)))); } else { @@ -109,19 +116,24 @@ public final class OidcClientRegistrationRegisteredClientConverter .requireAuthorizationConsent(true); if (ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - MacAlgorithm macAlgorithm = MacAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()); + String signingAlgorithm = clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm(); + MacAlgorithm macAlgorithm = (signingAlgorithm != null) ? MacAlgorithm.from(signingAlgorithm) : null; if (macAlgorithm == null) { macAlgorithm = MacAlgorithm.HS256; } clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(macAlgorithm); } else if (ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue().equals(clientRegistration.getTokenEndpointAuthenticationMethod())) { - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.from(clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm()); + String signingAlgorithm = clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm(); + SignatureAlgorithm signatureAlgorithm = (signingAlgorithm != null) + ? SignatureAlgorithm.from(signingAlgorithm) : null; if (signatureAlgorithm == null) { signatureAlgorithm = SignatureAlgorithm.RS256; } clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(signatureAlgorithm); - clientSettingsBuilder.jwkSetUrl(clientRegistration.getJwkSetUrl().toString()); + URL jwkSetUrl = clientRegistration.getJwkSetUrl(); + Assert.notNull(jwkSetUrl, "jwkSetUrl cannot be null"); + clientSettingsBuilder.jwkSetUrl(jwkSetUrl.toString()); } builder diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java index 77e9e8a269..048d17bd30 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/RegisteredClientOidcClientRegistrationConverter.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.converter; +import java.time.Instant; + import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; @@ -43,8 +45,11 @@ public final class RegisteredClientOidcClientRegistrationConverter // @formatter:off OidcClientRegistration.Builder builder = OidcClientRegistration.builder() .clientId(registeredClient.getClientId()) - .clientIdIssuedAt(registeredClient.getClientIdIssuedAt()) .clientName(registeredClient.getClientName()); + Instant clientIdIssuedAt = registeredClient.getClientIdIssuedAt(); + if (clientIdIssuedAt != null) { + builder.clientIdIssuedAt(clientIdIssuedAt); + } if (registeredClient.getClientSecret() != null) { builder.clientSecret(registeredClient.getClientSecret()); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/package-info.java new file mode 100644 index 0000000000..dcbd482b14 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/converter/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * {@link org.springframework.core.convert.converter.Converter} implementations for OpenID + * Connect 1.0 domain types. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc.converter; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java index f442a6636e..174f794692 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.http.converter; +import org.jspecify.annotations.Nullable; + import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; @@ -54,7 +56,7 @@ final class HttpMessageConverters { } @SuppressWarnings("removal") - static GenericHttpMessageConverter getJsonMessageConverter() { + static @Nullable GenericHttpMessageConverter getJsonMessageConverter() { if (jacksonPresent) { return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter()); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java index 37c741d886..292b054e6f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java @@ -26,6 +26,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; @@ -60,8 +62,7 @@ public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMess private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OidcClientRegistration> clientRegistrationConverter = new MapOidcClientRegistrationConverter(); @@ -69,6 +70,9 @@ public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMess public OidcClientRegistrationHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override @@ -188,7 +192,7 @@ public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMess return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor); } - private static Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) { + private static @Nullable Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) { if (clientSecretExpiresAt != null && String.valueOf(clientSecretExpiresAt).equals("0")) { // 0 indicates that client_secret_expires_at does not expire return null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java index 99a138d9c0..c3e353f954 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java @@ -53,8 +53,7 @@ public class OidcProviderConfigurationHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OidcProviderConfiguration> providerConfigurationConverter = new OidcProviderConfigurationConverter(); @@ -62,6 +61,9 @@ public class OidcProviderConfigurationHttpMessageConverter public OidcProviderConfigurationHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java index 86f8867665..979aea542d 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java @@ -52,8 +52,7 @@ public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConvert private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters - .getJsonMessageConverter(); + private final GenericHttpMessageConverter jsonMessageConverter; private Converter, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter(); @@ -61,6 +60,9 @@ public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConvert public OidcUserInfoHttpMessageConverter() { super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + this.jsonMessageConverter = converter; } @Override diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/package-info.java new file mode 100644 index 0000000000..a1045a0e11 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * HTTP message converters for OpenID Connect 1.0 protocol representations. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc.http.converter; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/package-info.java new file mode 100644 index 0000000000..fb86ce590b --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * OpenID Connect 1.0 support for the authorization server (subpackages provide web, + * authentication, and HTTP conversion). + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java index adfe3b6fe2..8e13ada967 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java @@ -143,6 +143,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi try { Authentication clientRegistrationAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(clientRegistrationAuthentication, "clientRegistrationAuthentication cannot be null"); Authentication clientRegistrationAuthenticationResult = this.authenticationManager .authenticate(clientRegistrationAuthentication); @@ -212,6 +213,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi Authentication authentication) throws IOException { OidcClientRegistration clientRegistration = ((OidcClientRegistrationAuthenticationToken) authentication) .getClientRegistration(); + Assert.notNull(clientRegistration, "clientRegistration cannot be null"); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); if (HttpMethod.POST.name().equals(request.getMethod())) { httpResponse.setStatusCode(HttpStatus.CREATED); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java index c9333749b1..836c1a688b 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcLogoutEndpointFilter.java @@ -109,6 +109,7 @@ public final class OidcLogoutEndpointFilter extends OncePerRequestFilter { try { Authentication oidcLogoutAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(oidcLogoutAuthentication, "oidcLogoutAuthentication cannot be null"); Authentication oidcLogoutAuthenticationResult = this.authenticationManager .authenticate(oidcLogoutAuthentication); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java index cc00e80743..52551e347c 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java @@ -116,6 +116,7 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter { try { Authentication userInfoAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(userInfoAuthentication, "userInfoAuthentication cannot be null"); Authentication userInfoAuthenticationResult = this.authenticationManager .authenticate(userInfoAuthentication); @@ -181,14 +182,17 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter { private Authentication createAuthentication(HttpServletRequest request) { Authentication principal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(principal, "current principal cannot be null"); return new OidcUserInfoAuthenticationToken(principal); } private void sendUserInfoResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { OidcUserInfoAuthenticationToken userInfoAuthenticationToken = (OidcUserInfoAuthenticationToken) authentication; + OidcUserInfo userInfo = userInfoAuthenticationToken.getUserInfo(); + Assert.notNull(userInfo, "userInfo cannot be null"); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - this.userInfoHttpMessageConverter.write(userInfoAuthenticationToken.getUserInfo(), null, httpResponse); + this.userInfoHttpMessageConverter.write(userInfo, null, httpResponse); } private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java index 60891d3f38..0936931975 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcClientRegistrationAuthenticationConverter.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.web.authentication; +import java.util.List; + import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.converter.HttpMessageConverter; @@ -31,6 +33,7 @@ import org.springframework.security.oauth2.server.authorization.oidc.authenticat import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -52,6 +55,7 @@ public final class OidcClientRegistrationAuthenticationConverter implements Auth @Override public Authentication convert(HttpServletRequest request) { Authentication principal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(principal, "current principal cannot be null"); if ("POST".equals(request.getMethod())) { OidcClientRegistration clientRegistration; @@ -72,7 +76,8 @@ public final class OidcClientRegistrationAuthenticationConverter implements Auth // client_id (REQUIRED) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParameters = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParameters == null || clientIdParameters.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java index cc7d8a6c0d..ae1c118128 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationConverter.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.oidc.web.authentication; +import java.util.List; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; @@ -56,8 +58,9 @@ public final class OidcLogoutAuthenticationConverter implements AuthenticationCo // id_token_hint (REQUIRED) // RECOMMENDED as per spec String idTokenHint = parameters.getFirst("id_token_hint"); - if (!StringUtils.hasText(idTokenHint) || parameters.get("id_token_hint").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "id_token_hint"); + List idTokenHintParameters = parameters.get("id_token_hint"); + if (!StringUtils.hasText(idTokenHint) || idTokenHintParameters == null || idTokenHintParameters.size() != 1) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, "id_token_hint"); } Authentication principal = SecurityContextHolder.getContext().getAuthentication(); @@ -73,30 +76,34 @@ public final class OidcLogoutAuthenticationConverter implements AuthenticationCo // client_id (OPTIONAL) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (StringUtils.hasText(clientId) && parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); + List clientIdParameters = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (StringUtils.hasText(clientId) && (clientIdParameters == null || clientIdParameters.size() != 1)) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); } // post_logout_redirect_uri (OPTIONAL) String postLogoutRedirectUri = parameters.getFirst("post_logout_redirect_uri"); - if (StringUtils.hasText(postLogoutRedirectUri) && parameters.get("post_logout_redirect_uri").size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri"); + List postLogoutRedirectUriParameters = parameters.get("post_logout_redirect_uri"); + if (StringUtils.hasText(postLogoutRedirectUri) + && (postLogoutRedirectUriParameters == null || postLogoutRedirectUriParameters.size() != 1)) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri"); } // state (OPTIONAL) String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); + List stateParameters = parameters.get(OAuth2ParameterNames.STATE); + if (StringUtils.hasText(state) && (stateParameters == null || stateParameters.size() != 1)) { + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); } return new OidcLogoutAuthenticationToken(idTokenHint, principal, sessionId, clientId, postLogoutRedirectUri, state); } - private static void throwError(String errorCode, String parameterName) { + private static OAuth2AuthenticationException createException(String errorCode, String parameterName) { OAuth2Error error = new OAuth2Error(errorCode, "OpenID Connect 1.0 Logout Request Parameter: " + parameterName, "https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ValidationAndErrorHandling"); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java index e95548f6ed..f3a99edd1d 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/OidcLogoutAuthenticationSuccessHandler.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -92,8 +93,10 @@ public final class OidcLogoutAuthenticationSuccessHandler implements Authenticat } private void performLogout(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication; + @Nullable Authentication authentication) { + if (!(authentication instanceof OidcLogoutAuthenticationToken oidcLogoutAuthentication)) { + return; + } // Check for active user session if (oidcLogoutAuthentication.isPrincipalAuthenticated()) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/package-info.java new file mode 100644 index 0000000000..e83e6f554e --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/authentication/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * {@link org.springframework.security.web.authentication.AuthenticationConverter} + * implementations for OpenID Connect 1.0 endpoints. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc.web.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/package-info.java new file mode 100644 index 0000000000..079cef82ac --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Servlet filters and web support for OpenID Connect 1.0 endpoints. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.oidc.web; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/package-info.java new file mode 100644 index 0000000000..176eb901af --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Support for an OAuth2 Authorization Server, including authorization persistence, + * consent, token services, and protocol endpoints. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java index 36cea950ec..d26479abd3 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AbstractSettings.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -51,7 +53,7 @@ public abstract class AbstractSettings implements Serializable { * @return the value of the setting, or {@code null} if not available */ @SuppressWarnings("unchecked") - public T getSetting(String name) { + public @Nullable T getSetting(String name) { Assert.hasText(name, "name cannot be empty"); return (T) getSettings().get(name); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java index b7abf76da6..a920a4334d 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.authorization.settings; import java.io.Serial; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; import org.springframework.util.Assert; @@ -42,9 +44,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { /** * Returns the URL of the Authorization Server's Issuer Identifier. - * @return the URL of the Authorization Server's Issuer Identifier + * @return the URL of the Authorization Server's Issuer Identifier, or {@code null} + * when using multiple issuers per host */ - public String getIssuer() { + public @Nullable String getIssuer() { return getSetting(ConfigurationSettingNames.AuthorizationServer.ISSUER); } @@ -64,7 +67,7 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @see AuthorizationServerContext#getIssuer() */ public boolean isMultipleIssuersAllowed() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED); + return Boolean.TRUE.equals(getSetting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED)); } /** @@ -73,7 +76,9 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Authorization endpoint */ public String getAuthorizationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT); + String authorizationEndpoint = getSetting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT); + Assert.notNull(authorizationEndpoint, "authorizationEndpoint cannot be null"); + return authorizationEndpoint; } /** @@ -82,7 +87,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Pushed Authorization Request endpoint */ public String getPushedAuthorizationRequestEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT); + String pushedAuthorizationRequestEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT); + Assert.notNull(pushedAuthorizationRequestEndpoint, "pushedAuthorizationRequestEndpoint cannot be null"); + return pushedAuthorizationRequestEndpoint; } /** @@ -91,7 +99,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Device Authorization endpoint */ public String getDeviceAuthorizationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT); + String deviceAuthorizationEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.DEVICE_AUTHORIZATION_ENDPOINT); + Assert.notNull(deviceAuthorizationEndpoint, "deviceAuthorizationEndpoint cannot be null"); + return deviceAuthorizationEndpoint; } /** @@ -100,7 +111,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Device Verification endpoint */ public String getDeviceVerificationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT); + String deviceVerificationEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.DEVICE_VERIFICATION_ENDPOINT); + Assert.notNull(deviceVerificationEndpoint, "deviceVerificationEndpoint cannot be null"); + return deviceVerificationEndpoint; } /** @@ -108,7 +122,9 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Token endpoint */ public String getTokenEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT); + String tokenEndpoint = getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT); + Assert.notNull(tokenEndpoint, "tokenEndpoint cannot be null"); + return tokenEndpoint; } /** @@ -116,7 +132,9 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the JWK Set endpoint */ public String getJwkSetEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT); + String jwkSetEndpoint = getSetting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT); + Assert.notNull(jwkSetEndpoint, "jwkSetEndpoint cannot be null"); + return jwkSetEndpoint; } /** @@ -125,7 +143,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Token Revocation endpoint */ public String getTokenRevocationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT); + String tokenRevocationEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT); + Assert.notNull(tokenRevocationEndpoint, "tokenRevocationEndpoint cannot be null"); + return tokenRevocationEndpoint; } /** @@ -134,7 +155,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the Token Introspection endpoint */ public String getTokenIntrospectionEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT); + String tokenIntrospectionEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT); + Assert.notNull(tokenIntrospectionEndpoint, "tokenIntrospectionEndpoint cannot be null"); + return tokenIntrospectionEndpoint; } /** @@ -143,7 +167,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the OAuth 2.0 Dynamic Client Registration endpoint */ public String getClientRegistrationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.CLIENT_REGISTRATION_ENDPOINT); + String clientRegistrationEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.CLIENT_REGISTRATION_ENDPOINT); + Assert.notNull(clientRegistrationEndpoint, "clientRegistrationEndpoint cannot be null"); + return clientRegistrationEndpoint; } /** @@ -152,7 +179,10 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the OpenID Connect 1.0 Client Registration endpoint */ public String getOidcClientRegistrationEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT); + String oidcClientRegistrationEndpoint = getSetting( + ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT); + Assert.notNull(oidcClientRegistrationEndpoint, "oidcClientRegistrationEndpoint cannot be null"); + return oidcClientRegistrationEndpoint; } /** @@ -160,7 +190,9 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the OpenID Connect 1.0 UserInfo endpoint */ public String getOidcUserInfoEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT); + String oidcUserInfoEndpoint = getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT); + Assert.notNull(oidcUserInfoEndpoint, "oidcUserInfoEndpoint cannot be null"); + return oidcUserInfoEndpoint; } /** @@ -169,7 +201,9 @@ public final class AuthorizationServerSettings extends AbstractSettings { * @return the OpenID Connect 1.0 Logout endpoint */ public String getOidcLogoutEndpoint() { - return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT); + String oidcLogoutEndpoint = getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_LOGOUT_ENDPOINT); + Assert.notNull(oidcLogoutEndpoint, "oidcLogoutEndpoint cannot be null"); + return oidcLogoutEndpoint; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java index 67e1ecbed6..665300fc04 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ClientSettings.java @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.authorization.settings; import java.io.Serial; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; import org.springframework.security.oauth2.jwt.Jwt; @@ -49,7 +51,7 @@ public final class ClientSettings extends AbstractSettings { * verifier, {@code false} otherwise */ public boolean isRequireProofKey() { - return getSetting(ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY); + return Boolean.TRUE.equals(getSetting(ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY)); } /** @@ -59,14 +61,15 @@ public final class ClientSettings extends AbstractSettings { * access, {@code false} otherwise */ public boolean isRequireAuthorizationConsent() { - return getSetting(ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT); + return Boolean.TRUE.equals(getSetting(ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT)); } /** * Returns the {@code URL} for the Client's JSON Web Key Set. - * @return the {@code URL} for the Client's JSON Web Key Set + * @return the {@code URL} for the Client's JSON Web Key Set, or {@code null} if not + * set */ - public String getJwkSetUrl() { + public @Nullable String getJwkSetUrl() { return getSetting(ConfigurationSettingNames.Client.JWK_SET_URL); } @@ -77,9 +80,10 @@ public final class ClientSettings extends AbstractSettings { * {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} * authentication methods. * @return the {@link JwsAlgorithm JWS} algorithm that must be used for signing the - * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint + * {@link Jwt JWT} used to authenticate the Client at the Token Endpoint, or + * {@code null} if not set */ - public JwsAlgorithm getTokenEndpointAuthenticationSigningAlgorithm() { + public @Nullable JwsAlgorithm getTokenEndpointAuthenticationSigningAlgorithm() { return getSetting(ConfigurationSettingNames.Client.TOKEN_ENDPOINT_AUTHENTICATION_SIGNING_ALGORITHM); } @@ -88,9 +92,10 @@ public final class ClientSettings extends AbstractSettings { * {@code X509Certificate} received during client authentication when using the * {@code tls_client_auth} method. * @return the expected subject distinguished name associated to the client - * {@code X509Certificate} received during client authentication + * {@code X509Certificate} received during client authentication, or {@code null} if + * not set */ - public String getX509CertificateSubjectDN() { + public @Nullable String getX509CertificateSubjectDN() { return getSetting(ConfigurationSettingNames.Client.X509_CERTIFICATE_SUBJECT_DN); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java index 1716679ae1..5133308706 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java @@ -46,7 +46,10 @@ public final class TokenSettings extends AbstractSettings { * @return the time-to-live for an authorization code */ public Duration getAuthorizationCodeTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE); + Duration authorizationCodeTimeToLive = getSetting( + ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE); + Assert.notNull(authorizationCodeTimeToLive, "authorizationCodeTimeToLive cannot be null"); + return authorizationCodeTimeToLive; } /** @@ -54,7 +57,9 @@ public final class TokenSettings extends AbstractSettings { * @return the time-to-live for an access token */ public Duration getAccessTokenTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE); + Duration accessTokenTimeToLive = getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE); + Assert.notNull(accessTokenTimeToLive, "accessTokenTimeToLive cannot be null"); + return accessTokenTimeToLive; } /** @@ -63,7 +68,9 @@ public final class TokenSettings extends AbstractSettings { * @return the token format for an access token */ public OAuth2TokenFormat getAccessTokenFormat() { - return getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT); + OAuth2TokenFormat accessTokenFormat = getSetting(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT); + Assert.notNull(accessTokenFormat, "accessTokenFormat cannot be null"); + return accessTokenFormat; } /** @@ -71,7 +78,9 @@ public final class TokenSettings extends AbstractSettings { * @return the time-to-live for a device code */ public Duration getDeviceCodeTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE); + Duration deviceCodeTimeToLive = getSetting(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE); + Assert.notNull(deviceCodeTimeToLive, "deviceCodeTimeToLive cannot be null"); + return deviceCodeTimeToLive; } /** @@ -82,7 +91,7 @@ public final class TokenSettings extends AbstractSettings { * response, {@code false} otherwise */ public boolean isReuseRefreshTokens() { - return getSetting(ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS); + return Boolean.TRUE.equals(getSetting(ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS)); } /** @@ -90,7 +99,9 @@ public final class TokenSettings extends AbstractSettings { * @return the time-to-live for a refresh token */ public Duration getRefreshTokenTimeToLive() { - return getSetting(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE); + Duration refreshTokenTimeToLive = getSetting(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE); + Assert.notNull(refreshTokenTimeToLive, "refreshTokenTimeToLive cannot be null"); + return refreshTokenTimeToLive; } /** @@ -101,7 +112,10 @@ public final class TokenSettings extends AbstractSettings { * {@link OidcIdToken ID Token} */ public SignatureAlgorithm getIdTokenSignatureAlgorithm() { - return getSetting(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM); + SignatureAlgorithm idTokenSignatureAlgorithm = getSetting( + ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM); + Assert.notNull(idTokenSignatureAlgorithm, "idTokenSignatureAlgorithm cannot be null"); + return idTokenSignatureAlgorithm; } /** @@ -113,7 +127,7 @@ public final class TokenSettings extends AbstractSettings { * {@code X509Certificate}, {@code false} otherwise */ public boolean isX509CertificateBoundAccessTokens() { - return getSetting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS); + return Boolean.TRUE.equals(getSetting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS)); } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/package-info.java new file mode 100644 index 0000000000..af727bb97b --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Configuration settings for registered clients and the authorization server. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.settings; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java index afa8166db4..ea427caff5 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenContext.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -39,9 +40,8 @@ public final class DefaultOAuth2TokenContext implements OAuth2TokenContext { } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java index 88ad058b0f..d4b2cf5483 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DelegatingOAuth2TokenGenerator.java @@ -20,7 +20,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.util.Assert; @@ -53,9 +54,8 @@ public final class DelegatingOAuth2TokenGenerator implements OAuth2TokenGenerato this.tokenGenerators = Collections.unmodifiableList(asList(tokenGenerators)); } - @Nullable @Override - public OAuth2Token generate(OAuth2TokenContext context) { + public @Nullable OAuth2Token generate(OAuth2TokenContext context) { for (OAuth2TokenGenerator tokenGenerator : this.tokenGenerators) { OAuth2Token token = tokenGenerator.generate(context); if (token != null) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java index 310b8644b0..84c3b01130 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtEncodingContext.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; @@ -47,9 +48,8 @@ public final class JwtEncodingContext implements OAuth2TokenContext { } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -65,7 +65,9 @@ public final class JwtEncodingContext implements OAuth2TokenContext { * @return the {@link JwsHeader.Builder} */ public JwsHeader.Builder getJwsHeader() { - return get(JwsHeader.Builder.class); + JwsHeader.Builder jwsHeaderBuilder = get(JwsHeader.Builder.class); + Assert.notNull(jwsHeaderBuilder, "jwsHeaderBuilder cannot be null"); + return jwsHeaderBuilder; } /** @@ -74,7 +76,9 @@ public final class JwtEncodingContext implements OAuth2TokenContext { * @return the {@link JwtClaimsSet.Builder} */ public JwtClaimsSet.Builder getClaims() { - return get(JwtClaimsSet.Builder.class); + JwtClaimsSet.Builder claimsBuilder = get(JwtClaimsSet.Builder.class); + Assert.notNull(claimsBuilder, "claimsBuilder cannot be null"); + return claimsBuilder; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java index cc400919a8..63bf4ff028 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java @@ -23,7 +23,9 @@ import java.util.Collections; import java.util.Date; import java.util.UUID; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + +import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -39,6 +41,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; @@ -64,7 +67,7 @@ public final class JwtGenerator implements OAuth2TokenGenerator { private final JwtEncoder jwtEncoder; - private OAuth2TokenCustomizer jwtCustomizer; + private @Nullable OAuth2TokenCustomizer jwtCustomizer; private Clock clock = Clock.systemUTC(); @@ -77,13 +80,11 @@ public final class JwtGenerator implements OAuth2TokenGenerator { this.jwtEncoder = jwtEncoder; } - @Nullable @Override - public Jwt generate(OAuth2TokenContext context) { + public @Nullable Jwt generate(OAuth2TokenContext context) { // @formatter:off - if (context.getTokenType() == null || - (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && - !OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue()))) { + if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && + !OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { return null; } if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && @@ -92,33 +93,30 @@ public final class JwtGenerator implements OAuth2TokenGenerator { } // @formatter:on - String issuer = null; - if (context.getAuthorizationServerContext() != null) { - issuer = context.getAuthorizationServerContext().getIssuer(); - } RegisteredClient registeredClient = context.getRegisteredClient(); - + String issuer = context.getAuthorizationServerContext().getIssuer(); Instant issuedAt = this.clock.instant(); Instant expiresAt; JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256; if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { // TODO Allow configuration for ID Token time-to-live expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - if (registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) { - jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm(); - } + jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm(); } else { expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); } + Authentication principal = context.getPrincipal(); + Assert.notNull(principal, "principal cannot be null"); + + AuthorizationGrantType authorizationGrantType = context.getAuthorizationGrantType(); + Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); + // @formatter:off - JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder(); - if (StringUtils.hasText(issuer)) { - claimsBuilder.issuer(issuer); - } - claimsBuilder - .subject(context.getPrincipal().getName()) + JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder() + .issuer(issuer) + .subject(principal.getName()) .audience(Collections.singletonList(registeredClient.getClientId())) .issuedAt(issuedAt) .expiresAt(expiresAt) @@ -131,9 +129,12 @@ public final class JwtGenerator implements OAuth2TokenGenerator { } else if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { claimsBuilder.claim(IdTokenClaimNames.AZP, registeredClient.getClientId()); - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType())) { - OAuth2AuthorizationRequest authorizationRequest = context.getAuthorization().getAttribute( + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) { + OAuth2Authorization authorization = context.getAuthorization(); + Assert.notNull(authorization, "authorization cannot be null"); + OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute( OAuth2AuthorizationRequest.class.getName()); + Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE); if (StringUtils.hasText(nonce)) { claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce); @@ -144,13 +145,19 @@ public final class JwtGenerator implements OAuth2TokenGenerator { claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, sessionInformation.getLastRequest()); } } - else if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { - OidcIdToken currentIdToken = context.getAuthorization().getToken(OidcIdToken.class).getToken(); - if (currentIdToken.hasClaim("sid")) { - claimsBuilder.claim("sid", currentIdToken.getClaim("sid")); + else if (AuthorizationGrantType.REFRESH_TOKEN.equals(authorizationGrantType)) { + OAuth2Authorization authorization = context.getAuthorization(); + Assert.notNull(authorization, "authorization cannot be null"); + OAuth2Authorization.Token authorizedIdToken = authorization.getToken(OidcIdToken.class); + Assert.notNull(authorizedIdToken, "authorizedIdToken cannot be null"); + OidcIdToken currentIdToken = authorizedIdToken.getToken(); + String sidClaim = currentIdToken.getClaim("sid"); + if (sidClaim != null) { + claimsBuilder.claim("sid", sidClaim); } - if (currentIdToken.hasClaim(IdTokenClaimNames.AUTH_TIME)) { - claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, currentIdToken.getClaim(IdTokenClaimNames.AUTH_TIME)); + Date authTimeClaim = currentIdToken.getClaim(IdTokenClaimNames.AUTH_TIME); + if (authTimeClaim != null) { + claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, authTimeClaim); } } } @@ -162,11 +169,11 @@ public final class JwtGenerator implements OAuth2TokenGenerator { // @formatter:off JwtEncodingContext.Builder jwtContextBuilder = JwtEncodingContext.with(jwsHeaderBuilder, claimsBuilder) .registeredClient(context.getRegisteredClient()) - .principal(context.getPrincipal()) + .principal(principal) .authorizationServerContext(context.getAuthorizationServerContext()) .authorizedScopes(context.getAuthorizedScopes()) .tokenType(context.getTokenType()) - .authorizationGrantType(context.getAuthorizationGrantType()); + .authorizationGrantType(authorizationGrantType); if (context.getAuthorization() != null) { jwtContextBuilder.authorization(context.getAuthorization()); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java index 669247019f..eb12f5c6b8 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java @@ -24,9 +24,12 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; @@ -36,7 +39,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; /** * An {@link OAuth2TokenGenerator} that generates a {@link OAuth2TokenFormat#REFERENCE @@ -55,13 +57,12 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator accessTokenCustomizer; + private @Nullable OAuth2TokenCustomizer accessTokenCustomizer; private Clock clock = Clock.systemUTC(); - @Nullable @Override - public OAuth2AccessToken generate(OAuth2TokenContext context) { + public @Nullable OAuth2AccessToken generate(OAuth2TokenContext context) { // @formatter:off if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { @@ -69,22 +70,22 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator claims; - private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, - Set scopes, Map claims) { + private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, @Nullable Instant issuedAt, + @Nullable Instant expiresAt, Set scopes, Map claims) { super(tokenType, tokenValue, issuedAt, expiresAt, scopes); this.claims = claims; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java index 06857c7f56..a4b323a488 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java @@ -20,7 +20,9 @@ import java.time.Clock; import java.time.Instant; import java.util.Base64; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -45,9 +47,8 @@ public final class OAuth2RefreshTokenGenerator implements OAuth2TokenGenerator getAudience() { + default @Nullable List getAudience() { return getClaimAsStringList(OAuth2TokenClaimNames.AUD); } @@ -65,9 +68,9 @@ public interface OAuth2TokenClaimAccessor extends ClaimAccessor { * Returns the Expiration time {@code (exp)} claim which identifies the expiration * time on or after which the OAuth 2.0 Token MUST NOT be accepted for processing. * @return the Expiration time on or after which the OAuth 2.0 Token MUST NOT be - * accepted for processing + * accepted for processing, or {@code null} if not present */ - default Instant getExpiresAt() { + default @Nullable Instant getExpiresAt() { return getClaimAsInstant(OAuth2TokenClaimNames.EXP); } @@ -75,9 +78,9 @@ public interface OAuth2TokenClaimAccessor extends ClaimAccessor { * Returns the Not Before {@code (nbf)} claim which identifies the time before which * the OAuth 2.0 Token MUST NOT be accepted for processing. * @return the Not Before time before which the OAuth 2.0 Token MUST NOT be accepted - * for processing + * for processing, or {@code null} if not present */ - default Instant getNotBefore() { + default @Nullable Instant getNotBefore() { return getClaimAsInstant(OAuth2TokenClaimNames.NBF); } @@ -85,18 +88,19 @@ public interface OAuth2TokenClaimAccessor extends ClaimAccessor { * Returns the Issued at {@code (iat)} claim which identifies the time at which the * OAuth 2.0 Token was issued. * @return the Issued at claim which identifies the time at which the OAuth 2.0 Token - * was issued + * was issued, or {@code null} if not present */ - default Instant getIssuedAt() { + default @Nullable Instant getIssuedAt() { return getClaimAsInstant(OAuth2TokenClaimNames.IAT); } /** * Returns the ID {@code (jti)} claim which provides a unique identifier for the OAuth * 2.0 Token. - * @return the ID claim which provides a unique identifier for the OAuth 2.0 Token + * @return the ID claim which provides a unique identifier for the OAuth 2.0 Token, or + * {@code null} if not present */ - default String getId() { + default @Nullable String getId() { return getClaimAsString(OAuth2TokenClaimNames.JTI); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java index cdb3995014..346c237964 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenClaimsContext.java @@ -20,7 +20,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -41,9 +42,8 @@ public final class OAuth2TokenClaimsContext implements OAuth2TokenContext { } @SuppressWarnings("unchecked") - @Nullable @Override - public V get(Object key) { + public @Nullable V get(Object key) { return hasKey(key) ? (V) this.context.get(key) : null; } @@ -59,7 +59,9 @@ public final class OAuth2TokenClaimsContext implements OAuth2TokenContext { * @return the {@link OAuth2TokenClaimsSet.Builder} */ public OAuth2TokenClaimsSet.Builder getClaims() { - return get(OAuth2TokenClaimsSet.Builder.class); + OAuth2TokenClaimsSet.Builder claimsBuilder = get(OAuth2TokenClaimsSet.Builder.class); + Assert.notNull(claimsBuilder, "claimsBuilder cannot be null"); + return claimsBuilder; } /** diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java index 97b143f1e7..f8e4a4dbaa 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenContext.java @@ -22,7 +22,8 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.jwt.Jwt; @@ -55,7 +56,9 @@ public interface OAuth2TokenContext extends Context { * @return the {@link RegisteredClient} */ default RegisteredClient getRegisteredClient() { - return get(RegisteredClient.class); + RegisteredClient registeredClient = get(RegisteredClient.class); + Assert.notNull(registeredClient, "registeredClient cannot be null"); + return registeredClient; } /** @@ -63,9 +66,9 @@ public interface OAuth2TokenContext extends Context { * owner (or client). * @param the type of the {@code Authentication} * @return the {@link Authentication} representing the {@code Principal} resource - * owner (or client) + * owner (or client), or {@code null} if not available */ - default T getPrincipal() { + default @Nullable T getPrincipal() { return get(AbstractBuilder.PRINCIPAL_AUTHENTICATION_KEY); } @@ -74,15 +77,16 @@ public interface OAuth2TokenContext extends Context { * @return the {@link AuthorizationServerContext} */ default AuthorizationServerContext getAuthorizationServerContext() { - return get(AuthorizationServerContext.class); + AuthorizationServerContext authorizationServerContext = get(AuthorizationServerContext.class); + Assert.notNull(authorizationServerContext, "authorizationServerContext cannot be null"); + return authorizationServerContext; } /** * Returns the {@link OAuth2Authorization authorization}. * @return the {@link OAuth2Authorization}, or {@code null} if not available */ - @Nullable - default OAuth2Authorization getAuthorization() { + default @Nullable OAuth2Authorization getAuthorization() { return get(OAuth2Authorization.class); } @@ -91,8 +95,8 @@ public interface OAuth2TokenContext extends Context { * @return the authorized scope(s) */ default Set getAuthorizedScopes() { - return hasKey(AbstractBuilder.AUTHORIZED_SCOPE_KEY) ? get(AbstractBuilder.AUTHORIZED_SCOPE_KEY) - : Collections.emptySet(); + Set authorizedScopes = get(AbstractBuilder.AUTHORIZED_SCOPE_KEY); + return (authorizedScopes != null) ? authorizedScopes : Collections.emptySet(); } /** @@ -100,23 +104,26 @@ public interface OAuth2TokenContext extends Context { * @return the {@link OAuth2TokenType} */ default OAuth2TokenType getTokenType() { - return get(OAuth2TokenType.class); + OAuth2TokenType tokenType = get(OAuth2TokenType.class); + Assert.notNull(tokenType, "tokenType cannot be null"); + return tokenType; } /** * Returns the {@link AuthorizationGrantType authorization grant type}. - * @return the {@link AuthorizationGrantType} + * @return the {@link AuthorizationGrantType}, or {@code null} if not available */ - default AuthorizationGrantType getAuthorizationGrantType() { + default @Nullable AuthorizationGrantType getAuthorizationGrantType() { return get(AuthorizationGrantType.class); } /** * Returns the {@link Authentication} representing the authorization grant. * @param the type of the {@code Authentication} - * @return the {@link Authentication} representing the authorization grant + * @return the {@link Authentication} representing the authorization grant, or + * {@code null} if not available */ - default T getAuthorizationGrant() { + default @Nullable T getAuthorizationGrant() { return get(AbstractBuilder.AUTHORIZATION_GRANT_AUTHENTICATION_KEY); } @@ -238,7 +245,7 @@ public interface OAuth2TokenContext extends Context { } @SuppressWarnings("unchecked") - protected V get(Object key) { + protected @Nullable V get(Object key) { return (V) this.context.get(key); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java index 0eaeeae4e1..9013b4ec08 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2TokenGenerator.java @@ -16,7 +16,8 @@ package org.springframework.security.oauth2.server.authorization.token; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; @@ -49,7 +50,6 @@ public interface OAuth2TokenGenerator { * @return an {@link OAuth2Token} or {@code null} if the * {@link OAuth2TokenContext#getTokenType()} is not supported */ - @Nullable - T generate(OAuth2TokenContext context); + @Nullable T generate(OAuth2TokenContext context); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/package-info.java new file mode 100644 index 0000000000..e63825009d --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * OAuth2 token generation and related support for access, refresh, and custom token + * types. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.token; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java index 5c8897f69c..f86d3e2724 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.web; +import org.jspecify.annotations.Nullable; + import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; @@ -53,7 +55,7 @@ final class HttpMessageConverters { } @SuppressWarnings("removal") - static GenericHttpMessageConverter getJsonMessageConverter() { + static @Nullable GenericHttpMessageConverter getJsonMessageConverter() { if (jacksonPresent) { return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter()); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java index 99de407539..e09ad4ed36 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java @@ -29,6 +29,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.http.HttpMethod; @@ -45,6 +46,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; @@ -124,7 +126,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte private SessionAuthenticationStrategy sessionAuthenticationStrategy = (authentication, request, response) -> { }; - private String consentPage; + private @Nullable String consentPage; /** * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided @@ -198,6 +200,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); } } + Assert.notNull(authentication, "authentication cannot be null"); Authentication authenticationResult = this.authenticationManager.authenticate(authentication); if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) { @@ -316,6 +319,9 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte else { requestedScopes = authorizationCodeRequestAuthentication.getScopes(); } + if (requestedScopes == null) { + requestedScopes = Collections.emptySet(); + } if (hasConsentUri()) { String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request)) @@ -339,6 +345,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte } private String resolveConsentUri(HttpServletRequest request) { + Assert.hasText(this.consentPage, "consentPage cannot be empty"); if (UrlUtils.isAbsoluteUrl(this.consentPage)) { return this.consentPage; } @@ -355,10 +362,12 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte Authentication authentication) throws IOException { OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = (OAuth2AuthorizationCodeRequestAuthenticationToken) authentication; - UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(authorizationCodeRequestAuthentication.getRedirectUri()) - .queryParam(OAuth2ParameterNames.CODE, - authorizationCodeRequestAuthentication.getAuthorizationCode().getTokenValue()); + String redirectUriForResponse = authorizationCodeRequestAuthentication.getRedirectUri(); + Assert.notNull(redirectUriForResponse, "redirectUri cannot be null"); + OAuth2AuthorizationCode authorizationCode = authorizationCodeRequestAuthentication.getAuthorizationCode(); + Assert.notNull(authorizationCode, "authorizationCode cannot be null"); + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(redirectUriForResponse) + .queryParam(OAuth2ParameterNames.CODE, authorizationCode.getTokenValue()); if (StringUtils.hasText(authorizationCodeRequestAuthentication.getState())) { uriBuilder.queryParam(OAuth2ParameterNames.STATE, UriUtils.encode(authorizationCodeRequestAuthentication.getState(), StandardCharsets.UTF_8)); @@ -429,8 +438,11 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte Assert.notNull(authenticationValidator, "authenticationValidator cannot be null"); this.registeredClientRepository = registeredClientRepository; this.authenticationValidator = authenticationValidator; - this.setValidatedField = ReflectionUtils.findField(OAuth2AuthorizationCodeRequestAuthenticationToken.class, + Field validatedField = ReflectionUtils.findField(OAuth2AuthorizationCodeRequestAuthenticationToken.class, "validated"); + Assert.notNull(validatedField, + "OAuth2AuthorizationCodeRequestAuthenticationToken.validated field cannot be resolved"); + this.setValidatedField = validatedField; ReflectionUtils.makeAccessible(this.setValidatedField); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientRegistrationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientRegistrationEndpointFilter.java index a94ca87e9f..acc571afae 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientRegistrationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientRegistrationEndpointFilter.java @@ -117,6 +117,7 @@ public final class OAuth2ClientRegistrationEndpointFilter extends OncePerRequest try { Authentication clientRegistrationAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(clientRegistrationAuthentication, "clientRegistrationAuthentication cannot be null"); Authentication clientRegistrationAuthenticationResult = this.authenticationManager .authenticate(clientRegistrationAuthentication); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java index c6df001411..65a327ccc5 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java @@ -130,6 +130,8 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques try { Authentication deviceAuthorizationRequestAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(deviceAuthorizationRequestAuthentication, + "deviceAuthorizationRequestAuthentication cannot be null"); if (deviceAuthorizationRequestAuthentication instanceof AbstractAuthenticationToken authenticationToken) { authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); } @@ -218,7 +220,9 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques OAuth2DeviceAuthorizationRequestAuthenticationToken deviceAuthorizationRequestAuthentication = (OAuth2DeviceAuthorizationRequestAuthenticationToken) authentication; OAuth2DeviceCode deviceCode = deviceAuthorizationRequestAuthentication.getDeviceCode(); + Assert.notNull(deviceCode, "deviceCode cannot be null"); OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode(); + Assert.notNull(userCode, "userCode cannot be null"); // Generate the fully-qualified verification URI UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(resolveVerificationUri(request)); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java index e0b01e0923..58901bd90c 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceVerificationEndpointFilter.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.web; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -26,6 +27,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.http.HttpMethod; @@ -100,7 +102,7 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; - private String consentPage; + private @Nullable String consentPage; /** * Constructs an {@code OAuth2DeviceVerificationEndpointFilter} using the provided @@ -156,6 +158,7 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest try { Authentication authentication = this.authenticationConverter.convert(request); + Assert.notNull(authentication, "authentication cannot be null"); if (authentication instanceof AbstractAuthenticationToken authenticationToken) { authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); } @@ -247,7 +250,8 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest String clientId = authorizationConsentAuthentication.getClientId(); Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal(); - Set requestedScopes = authorizationConsentAuthentication.getRequestedScopes(); + Set requestedScopes = (authorizationConsentAuthentication.getRequestedScopes() != null) + ? authorizationConsentAuthentication.getRequestedScopes() : Collections.emptySet(); Set authorizedScopes = authorizationConsentAuthentication.getScopes(); String state = authorizationConsentAuthentication.getState(); String userCode = authorizationConsentAuthentication.getUserCode(); @@ -277,6 +281,7 @@ public final class OAuth2DeviceVerificationEndpointFilter extends OncePerRequest } private String resolveConsentUri(HttpServletRequest request) { + Assert.hasText(this.consentPage, "consentPage cannot be empty"); if (UrlUtils.isAbsoluteUrl(this.consentPage)) { return this.consentPage; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java index 7e73dee08f..58b4384832 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java @@ -82,8 +82,13 @@ public final class OAuth2PushedAuthorizationRequestEndpointFilter extends OncePe private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private static final GenericHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters - .getJsonMessageConverter(); + private static final GenericHttpMessageConverter JSON_MESSAGE_CONVERTER; + + static { + GenericHttpMessageConverter converter = HttpMessageConverters.getJsonMessageConverter(); + Assert.notNull(converter, "Unable to locate a supported JSON message converter"); + JSON_MESSAGE_CONVERTER = converter; + } private final AuthenticationManager authenticationManager; @@ -134,6 +139,8 @@ public final class OAuth2PushedAuthorizationRequestEndpointFilter extends OncePe try { Authentication pushedAuthorizationRequestAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(pushedAuthorizationRequestAuthentication, + "pushedAuthorizationRequestAuthentication cannot be null"); if (pushedAuthorizationRequestAuthentication instanceof AbstractAuthenticationToken) { ((AbstractAuthenticationToken) pushedAuthorizationRequestAuthentication) .setDetails(this.authenticationDetailsSource.buildDetails(request)); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java index 270bdf2fc9..1ecda223dd 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java @@ -155,12 +155,12 @@ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { try { String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE); if (grantTypes == null || grantTypes.length != 1) { - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE); + throw createException(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE); } Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request); if (authorizationGrantAuthentication == null) { - throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE); + throw createException(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE); } if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken authenticationToken) { authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); @@ -228,9 +228,9 @@ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { this.authenticationFailureHandler = authenticationFailureHandler; } - private static void throwError(String errorCode, String parameterName) { + private static OAuth2AuthenticationException createException(String errorCode, String parameterName) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI); - throw new OAuth2AuthenticationException(error); + return new OAuth2AuthenticationException(error); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java index d8de09fc4a..0589de4367 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java @@ -115,6 +115,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest try { Authentication tokenIntrospectionAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(tokenIntrospectionAuthentication, "tokenIntrospectionAuthentication cannot be null"); Authentication tokenIntrospectionAuthenticationResult = this.authenticationManager .authenticate(tokenIntrospectionAuthentication); this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java index 052bafda61..83ddb98ed4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenRevocationEndpointFilter.java @@ -115,6 +115,7 @@ public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFil try { Authentication tokenRevocationAuthentication = this.authenticationConverter.convert(request); + Assert.notNull(tokenRevocationAuthentication, "tokenRevocationAuthentication cannot be null"); if (tokenRevocationAuthentication instanceof AbstractAuthenticationToken authenticationToken) { authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java index dc77643445..4cc3b81cba 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverter.java @@ -21,9 +21,9 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -48,9 +48,8 @@ import org.springframework.util.StringUtils; */ public final class ClientSecretBasicAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { String header = request.getHeader(HttpHeaders.AUTHORIZATION); if (header == null) { return null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java index 790da53233..639e9baf47 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverter.java @@ -16,11 +16,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -47,9 +48,8 @@ import org.springframework.util.StringUtils; */ public final class ClientSecretPostAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // client_id (REQUIRED) @@ -58,7 +58,8 @@ public final class ClientSecretPostAuthenticationConverter implements Authentica return null; } - if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (clientIdParams == null || clientIdParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } @@ -68,7 +69,8 @@ public final class ClientSecretPostAuthenticationConverter implements Authentica return null; } - if (parameters.get(OAuth2ParameterNames.CLIENT_SECRET).size() != 1) { + List clientSecretParams = parameters.get(OAuth2ParameterNames.CLIENT_SECRET); + if (clientSecretParams == null || clientSecretParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java index 39c358c927..688bbeec11 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/JwtClientAssertionAuthenticationConverter.java @@ -16,11 +16,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -48,9 +49,8 @@ public final class JwtClientAssertionAuthenticationConverter implements Authenti private static final ClientAuthenticationMethod JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD = new ClientAuthenticationMethod( "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); if (parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE) == null @@ -60,7 +60,8 @@ public final class JwtClientAssertionAuthenticationConverter implements Authenti // client_assertion_type (REQUIRED) String clientAssertionType = parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); - if (parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE).size() != 1) { + List clientAssertionTypeParams = parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE); + if (clientAssertionTypeParams == null || clientAssertionTypeParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } if (!JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD.getValue().equals(clientAssertionType)) { @@ -69,13 +70,15 @@ public final class JwtClientAssertionAuthenticationConverter implements Authenti // client_assertion (REQUIRED) String jwtAssertion = parameters.getFirst(OAuth2ParameterNames.CLIENT_ASSERTION); - if (parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION).size() != 1) { + List clientAssertionParams = parameters.get(OAuth2ParameterNames.CLIENT_ASSERTION); + if (clientAssertionParams == null || clientAssertionParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } // client_id (OPTIONAL as per specification but REQUIRED by this implementation) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java index 1c4a489c93..1aaa130de3 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AccessTokenResponseAuthenticationSuccessHandler.java @@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; @@ -59,7 +60,7 @@ public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler impleme private final HttpMessageConverter accessTokenResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - private Consumer accessTokenResponseCustomizer; + private @Nullable Consumer accessTokenResponseCustomizer; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java index 9da1fe81a3..1da897edd4 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java @@ -17,11 +17,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -30,6 +31,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -47,9 +49,8 @@ import org.springframework.util.StringUtils; */ public final class OAuth2AuthorizationCodeAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // grant_type (REQUIRED) @@ -58,20 +59,21 @@ public final class OAuth2AuthorizationCodeAuthenticationConverter implements Aut return null; } - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - // code (REQUIRED) String code = parameters.getFirst(OAuth2ParameterNames.CODE); - if (!StringUtils.hasText(code) || parameters.get(OAuth2ParameterNames.CODE).size() != 1) { + List codeParams = parameters.get(OAuth2ParameterNames.CODE); + if (!StringUtils.hasText(code) || codeParams == null || codeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CODE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } + Assert.notNull(code, "code cannot be null"); // redirect_uri (REQUIRED) // Required only if the "redirect_uri" parameter was included in the authorization // request String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { + List redirectUriParams = parameters.get(OAuth2ParameterNames.REDIRECT_URI); + if (StringUtils.hasText(redirectUri) && redirectUriParams != null && redirectUriParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -87,6 +89,9 @@ public final class OAuth2AuthorizationCodeAuthenticationConverter implements Aut // Validate DPoP Proof HTTP Header (if available) OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, redirectUri, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java index f5aac6dac6..ba7ecf5b69 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java @@ -19,11 +19,13 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -45,6 +47,7 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2Author import org.springframework.security.oauth2.server.authorization.web.OAuth2PushedAuthorizationRequestEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -76,7 +79,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme private final RequestMatcher requestMatcher = createDefaultRequestMatcher(); @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { if (!this.requestMatcher.matches(request)) { return null; } @@ -93,16 +96,20 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme if (pushedAuthorizationRequest) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI); } - else if (parameters.get(OAuth2ParameterNames.REQUEST_URI).size() != 1) { - // Authorization Request - throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI); + else { + List requestUriParams = parameters.get(OAuth2ParameterNames.REQUEST_URI); + if (requestUriParams == null || requestUriParams.size() != 1) { + // Authorization Request + throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUEST_URI); + } } } if (!StringUtils.hasText(requestUri)) { // response_type (REQUIRED) String responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE); - if (!StringUtils.hasText(responseType) || parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) { + List responseTypeParams = parameters.get(OAuth2ParameterNames.RESPONSE_TYPE); + if (!StringUtils.hasText(responseType) || responseTypeParams == null || responseTypeParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE); } else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) { @@ -114,9 +121,11 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme // client_id (REQUIRED) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); } + Assert.notNull(clientId, "clientId cannot be null"); Authentication principal = SecurityContextHolder.getContext().getAuthentication(); if (principal == null) { @@ -125,14 +134,16 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme // redirect_uri (OPTIONAL) String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) { + List redirectUriParams = parameters.get(OAuth2ParameterNames.REDIRECT_URI); + if (StringUtils.hasText(redirectUri) && redirectUriParams != null && redirectUriParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI); } // scope (OPTIONAL) Set scopes = null; String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + List scopeParams = parameters.get(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE); } if (StringUtils.hasText(scope)) { @@ -141,27 +152,31 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme // state (RECOMMENDED) String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) { + List stateParams = parameters.get(OAuth2ParameterNames.STATE); + if (StringUtils.hasText(state) && stateParams != null && stateParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); } // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE); - if (StringUtils.hasText(codeChallenge) && parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) { + List codeChallengeParams = parameters.get(PkceParameterNames.CODE_CHALLENGE); + if (StringUtils.hasText(codeChallenge) && codeChallengeParams != null && codeChallengeParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI); } // code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE) String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD); - if (StringUtils.hasText(codeChallengeMethod) - && parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) { + List codeChallengeMethodParams = parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD); + if (StringUtils.hasText(codeChallengeMethod) && codeChallengeMethodParams != null + && codeChallengeMethodParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI); } // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) { String prompt = parameters.getFirst("prompt"); - if (StringUtils.hasText(prompt) && parameters.get("prompt").size() != 1) { + List promptParams = parameters.get("prompt"); + if (StringUtils.hasText(prompt) && promptParams != null && promptParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt"); } } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java index e64872580b..d1c993af47 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationConsentAuthenticationConverter.java @@ -18,10 +18,12 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -36,6 +38,7 @@ import org.springframework.security.oauth2.server.authorization.authentication.O import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -61,7 +64,7 @@ public final class OAuth2AuthorizationConsentAuthenticationConverter implements private final RequestMatcher requestMatcher = createDefaultRequestMatcher(); @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { if (!this.requestMatcher.matches(request)) { return null; } @@ -72,9 +75,11 @@ public final class OAuth2AuthorizationConsentAuthenticationConverter implements // client_id (REQUIRED) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID); } + Assert.notNull(clientId, "clientId cannot be null"); Authentication principal = SecurityContextHolder.getContext().getAuthentication(); if (principal == null) { @@ -83,9 +88,11 @@ public final class OAuth2AuthorizationConsentAuthenticationConverter implements // state (REQUIRED) String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (!StringUtils.hasText(state) || parameters.get(OAuth2ParameterNames.STATE).size() != 1) { + List stateParams = parameters.get(OAuth2ParameterNames.STATE); + if (!StringUtils.hasText(state) || stateParams == null || stateParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE); } + Assert.notNull(state, "state cannot be null"); // scope (OPTIONAL) Set scopes = null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java index ff2a30c3fa..f1d0319ba1 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java @@ -19,12 +19,13 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -33,6 +34,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -50,9 +52,8 @@ import org.springframework.util.StringUtils; */ public final class OAuth2ClientCredentialsAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // grant_type (REQUIRED) @@ -61,11 +62,10 @@ public final class OAuth2ClientCredentialsAuthenticationConverter implements Aut return null; } - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - // scope (OPTIONAL) String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + List scopeParams = parameters.get(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -84,6 +84,9 @@ public final class OAuth2ClientCredentialsAuthenticationConverter implements Aut // Validate DPoP Proof HTTP Header (if available) OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScopes, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java index 16dd7295c8..21e115ac3f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationConsentAuthenticationConverter.java @@ -18,10 +18,12 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -32,6 +34,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -55,7 +58,7 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { if (!"POST".equals(request.getMethod()) || request.getParameter(OAuth2ParameterNames.STATE) == null) { return null; } @@ -66,9 +69,11 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple // client_id (REQUIRED) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, ERROR_URI); } + Assert.notNull(clientId, "clientId cannot be null"); Authentication principal = SecurityContextHolder.getContext().getAuthentication(); if (principal == null) { @@ -77,16 +82,20 @@ public final class OAuth2DeviceAuthorizationConsentAuthenticationConverter imple // user_code (REQUIRED) String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE); - if (!OAuth2EndpointUtils.validateUserCode(userCode) - || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) { + List userCodeParams = parameters.get(OAuth2ParameterNames.USER_CODE); + if (userCode == null || !OAuth2EndpointUtils.validateUserCode(userCode) || userCodeParams == null + || userCodeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USER_CODE, ERROR_URI); } + Assert.notNull(userCode, "userCode cannot be null"); // state (REQUIRED) String state = parameters.getFirst(OAuth2ParameterNames.STATE); - if (!StringUtils.hasText(state) || parameters.get(OAuth2ParameterNames.STATE).size() != 1) { + List stateParams = parameters.get(OAuth2ParameterNames.STATE); + if (!StringUtils.hasText(state) || stateParams == null || stateParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, ERROR_URI); } + Assert.notNull(state, "state cannot be null"); // scope (OPTIONAL) Set scopes = null; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java index b423856113..a396f8ae84 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceAuthorizationRequestAuthenticationConverter.java @@ -19,10 +19,12 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -31,6 +33,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceAuthorizationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -51,16 +54,15 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationConverter imple private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc8628#section-3.1"; @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); String authorizationUri = request.getRequestURL().toString(); // scope (OPTIONAL) String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + List scopeParams = parameters.get(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, ERROR_URI); } Set requestedScopes = null; @@ -75,6 +77,9 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationConverter imple } }); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2DeviceAuthorizationRequestAuthenticationToken(clientPrincipal, authorizationUri, requestedScopes, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java index 128bb4e256..4d4396cd06 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceCodeAuthenticationConverter.java @@ -17,11 +17,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -30,6 +31,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -47,9 +49,8 @@ import org.springframework.util.StringUtils; */ public final class OAuth2DeviceCodeAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // grant_type (REQUIRED) @@ -58,14 +59,14 @@ public final class OAuth2DeviceCodeAuthenticationConverter implements Authentica return null; } - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - // device_code (REQUIRED) String deviceCode = parameters.getFirst(OAuth2ParameterNames.DEVICE_CODE); - if (!StringUtils.hasText(deviceCode) || parameters.get(OAuth2ParameterNames.DEVICE_CODE).size() != 1) { + List deviceCodeParams = parameters.get(OAuth2ParameterNames.DEVICE_CODE); + if (!StringUtils.hasText(deviceCode) || deviceCodeParams == null || deviceCodeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.DEVICE_CODE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } + Assert.notNull(deviceCode, "deviceCode cannot be null"); Map additionalParameters = new HashMap<>(); parameters.forEach((key, value) -> { @@ -78,6 +79,9 @@ public final class OAuth2DeviceCodeAuthenticationConverter implements Authentica // Validate DPoP Proof HTTP Header (if available) OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2DeviceCodeAuthenticationToken(deviceCode, clientPrincipal, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java index ab5cb38649..a07af4aecd 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2DeviceVerificationAuthenticationConverter.java @@ -17,9 +17,11 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; @@ -30,6 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; /** @@ -52,7 +55,7 @@ public final class OAuth2DeviceVerificationAuthenticationConverter implements Au "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { if (!("GET".equals(request.getMethod()) || "POST".equals(request.getMethod()))) { return null; } @@ -66,10 +69,12 @@ public final class OAuth2DeviceVerificationAuthenticationConverter implements Au // user_code (REQUIRED) String userCode = parameters.getFirst(OAuth2ParameterNames.USER_CODE); - if (!OAuth2EndpointUtils.validateUserCode(userCode) - || parameters.get(OAuth2ParameterNames.USER_CODE).size() != 1) { + List userCodeParams = parameters.get(OAuth2ParameterNames.USER_CODE); + if (userCode == null || !OAuth2EndpointUtils.validateUserCode(userCode) || userCodeParams == null + || userCodeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USER_CODE, ERROR_URI); } + Assert.notNull(userCode, "userCode cannot be null"); Authentication principal = SecurityContextHolder.getContext().getAuthentication(); if (principal == null) { diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java index 65d895b525..6267932c31 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java @@ -19,12 +19,13 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -33,6 +34,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -50,9 +52,8 @@ import org.springframework.util.StringUtils; */ public final class OAuth2RefreshTokenAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // grant_type (REQUIRED) @@ -61,18 +62,19 @@ public final class OAuth2RefreshTokenAuthenticationConverter implements Authenti return null; } - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - // refresh_token (REQUIRED) String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN); - if (!StringUtils.hasText(refreshToken) || parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) { + List refreshTokenParams = parameters.get(OAuth2ParameterNames.REFRESH_TOKEN); + if (!StringUtils.hasText(refreshToken) || refreshTokenParams == null || refreshTokenParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } + Assert.notNull(refreshToken, "refreshToken cannot be null"); // scope (OPTIONAL) String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + List scopeParams = parameters.get(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -92,6 +94,9 @@ public final class OAuth2RefreshTokenAuthenticationConverter implements Authenti // Validate DPoP Proof HTTP Header (if available) OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java index a72e72353b..53e4966522 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java @@ -28,8 +28,8 @@ import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -40,6 +40,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -66,9 +67,8 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent private static final Set SUPPORTED_TOKEN_TYPES = Set.of(ACCESS_TOKEN_TYPE_VALUE, JWT_TOKEN_TYPE_VALUE); - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // grant_type (REQUIRED) @@ -77,8 +77,6 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent return null; } - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - // resource (OPTIONAL) List resources = parameters.getOrDefault(OAuth2ParameterNames.RESOURCE, Collections.emptyList()); if (!CollectionUtils.isEmpty(resources)) { @@ -95,11 +93,11 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent // scope (OPTIONAL) String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + List scopeParams = parameters.get(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } - Set requestedScopes = null; if (StringUtils.hasText(scope)) { requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); @@ -108,7 +106,8 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent // requested_token_type (OPTIONAL) String requestedTokenType = parameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE); if (StringUtils.hasText(requestedTokenType)) { - if (parameters.get(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE).size() != 1) { + List requestedTokenTypeParams = parameters.get(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE); + if (requestedTokenTypeParams == null || requestedTokenTypeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -118,28 +117,34 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent else { requestedTokenType = ACCESS_TOKEN_TYPE_VALUE; } + Assert.notNull(requestedTokenType, "requestedTokenType cannot be null"); // subject_token (REQUIRED) String subjectToken = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN); - if (!StringUtils.hasText(subjectToken) || parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN).size() != 1) { + List subjectTokenParams = parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN); + if (!StringUtils.hasText(subjectToken) || subjectTokenParams == null || subjectTokenParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } + Assert.notNull(subjectToken, "subjectToken cannot be null"); // subject_token_type (REQUIRED) String subjectTokenType = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE); - if (!StringUtils.hasText(subjectTokenType) - || parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE).size() != 1) { + List subjectTokenTypeParams = parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE); + if (!StringUtils.hasText(subjectTokenType) || subjectTokenTypeParams == null + || subjectTokenTypeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } else { validateTokenType(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, subjectTokenType); } + Assert.notNull(subjectTokenType, "subjectTokenType cannot be null"); // actor_token (OPTIONAL, REQUIRED if actor_token_type is provided) String actorToken = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN); - if (StringUtils.hasText(actorToken) && parameters.get(OAuth2ParameterNames.ACTOR_TOKEN).size() != 1) { + List actorTokenParams = parameters.get(OAuth2ParameterNames.ACTOR_TOKEN); + if (StringUtils.hasText(actorToken) && actorTokenParams != null && actorTokenParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -147,7 +152,8 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent // actor_token_type (OPTIONAL, REQUIRED if actor_token is provided) String actorTokenType = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE); if (StringUtils.hasText(actorTokenType)) { - if (parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE).size() != 1) { + List actorTokenTypeParams = parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE); + if (actorTokenTypeParams == null || actorTokenTypeParams.size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN_TYPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } @@ -180,6 +186,9 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent // Validate DPoP Proof HTTP Header (if available) OAuth2EndpointUtils.validateAndAddDPoPParametersIfAvailable(request, additionalParameters); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2TokenExchangeAuthenticationToken(requestedTokenType, subjectToken, subjectTokenType, clientPrincipal, actorToken, actorTokenType, new LinkedHashSet<>(resources), new LinkedHashSet<>(audiences), requestedScopes, additionalParameters); diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java index ec6e1f40a3..8b1696c454 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenIntrospectionAuthenticationConverter.java @@ -17,9 +17,11 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -30,6 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -48,20 +51,21 @@ import org.springframework.util.StringUtils; public final class OAuth2TokenIntrospectionAuthenticationConverter implements AuthenticationConverter { @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // token (REQUIRED) String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); - if (!StringUtils.hasText(token) || parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { + List tokenParams = parameters.get(OAuth2ParameterNames.TOKEN); + if (!StringUtils.hasText(token) || tokenParams == null || tokenParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); } + Assert.notNull(token, "token cannot be null"); // token_type_hint (OPTIONAL) String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); - if (StringUtils.hasText(tokenTypeHint) && parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { + List tokenTypeHintParams = parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT); + if (StringUtils.hasText(tokenTypeHint) && tokenTypeHintParams != null && tokenTypeHintParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); } @@ -72,6 +76,9 @@ public final class OAuth2TokenIntrospectionAuthenticationConverter implements Au } }); + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2TokenIntrospectionAuthenticationToken(token, clientPrincipal, tokenTypeHint, additionalParameters); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java index 7d8ba8f04f..0dcc6d5225 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenRevocationAuthenticationConverter.java @@ -16,7 +16,10 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; +import java.util.List; + import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -27,6 +30,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -45,23 +49,27 @@ import org.springframework.util.StringUtils; public final class OAuth2TokenRevocationAuthenticationConverter implements AuthenticationConverter { @Override - public Authentication convert(HttpServletRequest request) { - Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); - + public @Nullable Authentication convert(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getFormParameters(request); // token (REQUIRED) String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); - if (!StringUtils.hasText(token) || parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { + List tokenParams = parameters.get(OAuth2ParameterNames.TOKEN); + if (!StringUtils.hasText(token) || tokenParams == null || tokenParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); } + Assert.notNull(token, "token cannot be null"); // token_type_hint (OPTIONAL) String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); - if (StringUtils.hasText(tokenTypeHint) && parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { + List tokenTypeHintParams = parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT); + if (StringUtils.hasText(tokenTypeHint) && tokenTypeHintParams != null && tokenTypeHintParams.size() != 1) { throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); } + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java index a2046a23ee..cbb0420630 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java @@ -17,11 +17,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -48,9 +49,8 @@ import org.springframework.util.StringUtils; */ public final class PublicClientAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { if (!OAuth2EndpointUtils.matchesPkceTokenRequest(request)) { return null; } @@ -60,12 +60,14 @@ public final class PublicClientAuthenticationConverter implements Authentication // client_id (REQUIRED for public clients) String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } // code_verifier (REQUIRED) - if (parameters.get(PkceParameterNames.CODE_VERIFIER).size() != 1) { + List codeVerifierParams = parameters.get(PkceParameterNames.CODE_VERIFIER); + if (codeVerifierParams == null || codeVerifierParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java index 56792c202c..c38540004a 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java @@ -17,11 +17,12 @@ package org.springframework.security.oauth2.server.authorization.web.authentication; import java.security.cert.X509Certificate; +import java.util.List; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -47,9 +48,8 @@ import org.springframework.util.StringUtils; */ public final class X509ClientCertificateAuthenticationConverter implements AuthenticationConverter { - @Nullable @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { X509Certificate[] clientCertificateChain = (X509Certificate[]) request .getAttribute("jakarta.servlet.request.X509Certificate"); if (clientCertificateChain == null || clientCertificateChain.length == 0) { @@ -64,7 +64,8 @@ public final class X509ClientCertificateAuthenticationConverter implements Authe return null; } - if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) { + List clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID); + if (clientIdParams == null || clientIdParams.size() != 1) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/package-info.java new file mode 100644 index 0000000000..b138d5e8db --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * {@link org.springframework.security.web.authentication.AuthenticationConverter} + * implementations for OAuth2 Authorization Server endpoints. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.web.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/package-info.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/package-info.java new file mode 100644 index 0000000000..8cd9db0fc3 --- /dev/null +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-present 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. + */ + +/** + * Servlet filters and web support for OAuth2 Authorization Server endpoints. + */ +@NullMarked +package org.springframework.security.oauth2.server.authorization.web; + +import org.jspecify.annotations.NullMarked; diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java index c60b3781d7..2e7fe40a2c 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/context/TestAuthorizationServerContext.java @@ -18,7 +18,8 @@ package org.springframework.security.oauth2.server.authorization.context; import java.util.function.Supplier; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; /** diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java index 953d7703ee..b5e4807973 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java @@ -161,6 +161,14 @@ public class OidcClientRegistrationEndpointFilterTests { @Test public void doFilterWhenClientRegistrationRequestInvalidThenInvalidRequestError() throws Exception { + Jwt jwt = createJwt("client.create"); + JwtAuthenticationToken principal = new JwtAuthenticationToken(jwt, + AuthorityUtils.createAuthorityList("SCOPE_client.create")); + + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(principal); + SecurityContextHolder.setContext(securityContext); + String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); request.setServletPath(requestUri);