Browse Source

Enable null-safety in spring-security-oauth2-client

Closes gh-17819
pull/18821/merge
Joe Grandja 3 days ago
parent
commit
baad23caab
  1. 2
      config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java
  2. 10
      config/src/test/kotlin/org/springframework/security/config/annotation/web/oauth2/login/AuthorizationEndpointDslTests.kt
  3. 2
      docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt
  4. 1
      oauth2/oauth2-client/spring-security-oauth2-client.gradle
  5. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizationCodeOAuth2AuthorizedClientProvider.java
  6. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java
  7. 9
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java
  8. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java
  9. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/DelegatingOAuth2AuthorizedClientProvider.java
  10. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java
  11. 9
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryReactiveOAuth2AuthorizedClientService.java
  12. 26
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java
  13. 15
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java
  14. 12
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerReactiveOAuth2AuthorizedClientProvider.java
  15. 25
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java
  16. 28
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java
  17. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java
  18. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.java
  19. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProvider.java
  20. 16
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java
  21. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java
  22. 68
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/R2dbcReactiveOAuth2AuthorizedClientService.java
  23. 16
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java
  24. 27
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler.java
  25. 11
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java
  26. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java
  27. 20
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java
  28. 8
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java
  29. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/package-info.java
  30. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/aot/hint/package-info.java
  31. 8
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  32. 13
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationToken.java
  33. 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java
  34. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java
  35. 13
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationToken.java
  36. 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java
  37. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/package-info.java
  38. 6
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java
  39. 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java
  40. 10
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java
  41. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/package-info.java
  42. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/event/package-info.java
  43. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java
  44. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/package-info.java
  45. 53
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/ClientRegistrationDeserializer.java
  46. 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/JsonNodeUtils.java
  47. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/OAuth2AuthorizationRequestDeserializer.java
  48. 16
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/StdConverters.java
  49. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/package-info.java
  50. 51
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java
  51. 7
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/JsonNodeUtils.java
  52. 25
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestDeserializer.java
  53. 16
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java
  54. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/package-info.java
  55. 17
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java
  56. 9
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java
  57. 40
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java
  58. 22
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java
  59. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/event/package-info.java
  60. 25
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/LogoutTokenClaimAccessor.java
  61. 11
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/OidcLogoutToken.java
  62. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/package-info.java
  63. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/package-info.java
  64. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/server/session/package-info.java
  65. 19
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/session/InMemoryOidcSessionRegistry.java
  66. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/session/package-info.java
  67. 1
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java
  68. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/package-info.java
  69. 34
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java
  70. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/package-info.java
  71. 14
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java
  72. 23
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/package-info.java
  73. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/package-info.java
  74. 137
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java
  75. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrationRepository.java
  76. 20
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
  77. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryClientRegistrationRepository.java
  78. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierClientRegistrationRepository.java
  79. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/package-info.java
  80. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java
  81. 11
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java
  82. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DelegatingOAuth2UserService.java
  83. 8
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserRequestEntityConverter.java
  84. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserService.java
  85. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/package-info.java
  86. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java
  87. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java
  88. 4
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java
  89. 16
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
  90. 43
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java
  91. 21
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
  92. 9
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java
  93. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizedClientRepository.java
  94. 19
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java
  95. 18
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java
  96. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestResolver.java
  97. 30
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationResponseUtils.java
  98. 5
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizedClientRepository.java
  99. 3
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java
  100. 20
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/OAuth2ClientHttpRequestInterceptor.java
  101. Some files were not shown because too many files have changed in this diff Show More

2
config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java

@ -157,7 +157,7 @@ public class ClientRegistrationsBeanDefinitionParserTests { @@ -157,7 +157,7 @@ public class ClientRegistrationsBeanDefinitionParserTests {
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
assertThat(googleRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(googleRegistration.getRedirectUri()).isEqualTo("{baseUrl}/{action}/oauth2/code/{registrationId}");
assertThat(googleRegistration.getScopes()).isNull();
assertThat(googleRegistration.getScopes()).isEmpty();
assertThat(googleRegistration.getClientName()).isEqualTo(serverUrl);
ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails();
assertThat(googleProviderDetails).isNotNull();

10
config/src/test/kotlin/org/springframework/security/config/annotation/web/oauth2/login/AuthorizationEndpointDslTests.kt

@ -73,13 +73,11 @@ class AuthorizationEndpointDslTests { @@ -73,13 +73,11 @@ class AuthorizationEndpointDslTests {
companion object {
val RESOLVER: OAuth2AuthorizationRequestResolver = object : OAuth2AuthorizationRequestResolver {
override fun resolve(
request: HttpServletRequest?
) = OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(request: HttpServletRequest) =
OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(
request: HttpServletRequest?, clientRegistrationId: String?
) = OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(request: HttpServletRequest, clientRegistrationId: String) =
OAuth2AuthorizationRequest.authorizationCode().build()
}
}

2
docs/src/test/kotlin/org/springframework/security/kt/docs/features/integrations/rest/configurationwebclient/ServerWebClientHttpInterfaceIntegrationConfiguration.kt

@ -43,7 +43,7 @@ class ServerWebClientHttpInterfaceIntegrationConfiguration { @@ -43,7 +43,7 @@ class ServerWebClientHttpInterfaceIntegrationConfiguration {
fun securityConfigurer(
manager: ReactiveOAuth2AuthorizedClientManager?
): OAuth2WebClientHttpServiceGroupConfigurer {
return OAuth2WebClientHttpServiceGroupConfigurer.from(manager)
return OAuth2WebClientHttpServiceGroupConfigurer.from(requireNotNull(manager))
}
// end::config[]

1
oauth2/oauth2-client/spring-security-oauth2-client.gradle

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
plugins {
id 'javadoc-warnings-error'
id 'security-nullability'
}
apply plugin: 'io.spring.convention.spring-module'

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizationCodeOAuth2AuthorizedClientProvider.java

@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
package org.springframework.security.oauth2.client;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -47,8 +48,7 @@ public final class AuthorizationCodeOAuth2AuthorizedClientProvider implements OA @@ -47,8 +48,7 @@ public final class AuthorizationCodeOAuth2AuthorizedClientProvider implements OA
* the authorization request
*/
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
context.getClientRegistration().getAuthorizationGrantType()) && context.getAuthorizedClient() == null) {

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java

@ -21,7 +21,8 @@ import java.util.HashMap; @@ -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.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@ -113,9 +114,8 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen @@ -113,9 +114,8 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
.removeAuthorizedClient(clientRegistrationId, principal.getName()));
}
@Nullable
@Override
public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();

9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java

@ -20,7 +20,8 @@ import java.time.Clock; @@ -20,7 +20,8 @@ import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient;
@ -61,8 +62,7 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA @@ -61,8 +62,7 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA
* re-authorization) is not supported
*/
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
@ -98,7 +98,8 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA @@ -98,7 +98,8 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java

@ -89,7 +89,8 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider @@ -89,7 +89,8 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/DelegatingOAuth2AuthorizedClientProvider.java

@ -21,7 +21,8 @@ import java.util.Arrays; @@ -21,7 +21,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@ -64,8 +65,7 @@ public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2Aut @@ -64,8 +65,7 @@ public final class DelegatingOAuth2AuthorizedClientProvider implements OAuth2Aut
}
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
for (OAuth2AuthorizedClientProvider authorizedClientProvider : this.authorizedClientProviders) {
OAuth2AuthorizedClient oauth2AuthorizedClient = authorizedClientProvider.authorize(context);

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client; @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@ -72,7 +74,7 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author @@ -72,7 +74,7 @@ public final class InMemoryOAuth2AuthorizedClientService implements OAuth2Author
@Override
@SuppressWarnings("unchecked")
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
public <T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
String principalName) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");

9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryReactiveOAuth2AuthorizedClientService.java

@ -62,14 +62,15 @@ public final class InMemoryReactiveOAuth2AuthorizedClientService implements Reac @@ -62,14 +62,15 @@ public final class InMemoryReactiveOAuth2AuthorizedClientService implements Reac
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
return (Mono<T>) this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
.mapNotNull((clientRegistration) -> {
.flatMap((clientRegistration) -> {
OAuth2AuthorizedClientId id = new OAuth2AuthorizedClientId(clientRegistrationId, principalName);
OAuth2AuthorizedClient cachedAuthorizedClient = this.authorizedClients.get(id);
if (cachedAuthorizedClient == null) {
return null;
return Mono.empty();
}
return new OAuth2AuthorizedClient(clientRegistration, cachedAuthorizedClient.getPrincipalName(),
cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken());
return Mono
.just(new OAuth2AuthorizedClient(clientRegistration, cachedAuthorizedClient.getPrincipalName(),
cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken()));
});
}

26
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java

@ -29,6 +29,8 @@ import java.util.List; @@ -29,6 +29,8 @@ import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
@ -148,7 +150,7 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient @@ -148,7 +150,7 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
@Override
@SuppressWarnings("unchecked")
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
public <T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
String principalName) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
@ -265,16 +267,21 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient @@ -265,16 +267,21 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
tokenType = OAuth2AccessToken.TokenType.BEARER;
}
OAuth2AccessToken.TokenType tokenTypeToUse = (tokenType != null) ? tokenType
: OAuth2AccessToken.TokenType.BEARER;
String tokenValue = new String(this.lobHandler.getBlobAsBytes(rs, "access_token_value"),
StandardCharsets.UTF_8);
Instant issuedAt = rs.getTimestamp("access_token_issued_at").toInstant();
Instant expiresAt = rs.getTimestamp("access_token_expires_at").toInstant();
Timestamp issuedAtTs = rs.getTimestamp("access_token_issued_at");
Timestamp expiresAtTs = rs.getTimestamp("access_token_expires_at");
Instant issuedAt = (issuedAtTs != null) ? issuedAtTs.toInstant() : null;
Instant expiresAt = (expiresAtTs != null) ? expiresAtTs.toInstant() : null;
Set<String> scopes = Collections.emptySet();
String accessTokenScopes = rs.getString("access_token_scopes");
if (accessTokenScopes != null) {
scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, tokenValue, issuedAt, expiresAt, scopes);
OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenTypeToUse, tokenValue, issuedAt, expiresAt,
scopes);
OAuth2RefreshToken refreshToken = null;
byte[] refreshTokenValue = this.lobHandler.getBlobAsBytes(rs, "refresh_token_value");
if (refreshTokenValue != null) {
@ -312,8 +319,12 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient @@ -312,8 +319,12 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
parameters.add(new SqlParameterValue(Types.VARCHAR, accessToken.getTokenType().getValue()));
parameters
.add(new SqlParameterValue(Types.BLOB, accessToken.getTokenValue().getBytes(StandardCharsets.UTF_8)));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(accessToken.getIssuedAt())));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, Timestamp.from(accessToken.getExpiresAt())));
Instant accessTokenIssuedAt = accessToken.getIssuedAt();
Instant accessTokenExpiresAt = accessToken.getExpiresAt();
parameters.add(new SqlParameterValue(Types.TIMESTAMP,
(accessTokenIssuedAt != null) ? Timestamp.from(accessTokenIssuedAt) : null));
parameters.add(new SqlParameterValue(Types.TIMESTAMP,
(accessTokenExpiresAt != null) ? Timestamp.from(accessTokenExpiresAt) : null));
String accessTokenScopes = null;
if (!CollectionUtils.isEmpty(accessToken.getScopes())) {
accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getScopes(), ",");
@ -385,7 +396,8 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient @@ -385,7 +396,8 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
}
@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) {

15
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerOAuth2AuthorizedClientProvider.java

@ -21,7 +21,8 @@ import java.time.Duration; @@ -21,7 +21,8 @@ import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.RestClientJwtBearerTokenResponseClient;
@ -46,7 +47,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth @@ -46,7 +47,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
private OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = new RestClientJwtBearerTokenResponseClient();
private Function<OAuth2AuthorizationContext, Jwt> jwtAssertionResolver = this::resolveJwtAssertion;
private Function<OAuth2AuthorizationContext, @Nullable Jwt> jwtAssertionResolver = this::resolveJwtAssertion;
private Duration clockSkew = Duration.ofSeconds(60);
@ -65,8 +66,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth @@ -65,8 +66,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
* supported
*/
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
if (!AuthorizationGrantType.JWT_BEARER.equals(clientRegistration.getAuthorizationGrantType())) {
@ -100,7 +100,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth @@ -100,7 +100,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
tokenResponse.getAccessToken());
}
private Jwt resolveJwtAssertion(OAuth2AuthorizationContext context) {
private @Nullable Jwt resolveJwtAssertion(OAuth2AuthorizationContext context) {
if (!(context.getPrincipal().getPrincipal() instanceof Jwt)) {
return null;
}
@ -118,7 +118,8 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth @@ -118,7 +118,8 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**
@ -139,7 +140,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth @@ -139,7 +140,7 @@ public final class JwtBearerOAuth2AuthorizedClientProvider implements OAuth2Auth
* assertion
* @since 5.7
*/
public void setJwtAssertionResolver(Function<OAuth2AuthorizationContext, Jwt> jwtAssertionResolver) {
public void setJwtAssertionResolver(Function<OAuth2AuthorizationContext, @Nullable Jwt> jwtAssertionResolver) {
Assert.notNull(jwtAssertionResolver, "jwtAssertionResolver cannot be null");
this.jwtAssertionResolver = jwtAssertionResolver;
}

12
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JwtBearerReactiveOAuth2AuthorizedClientProvider.java

@ -106,14 +106,18 @@ public final class JwtBearerReactiveOAuth2AuthorizedClientProvider implements Re @@ -106,14 +106,18 @@ public final class JwtBearerReactiveOAuth2AuthorizedClientProvider implements Re
private Mono<Jwt> resolveJwtAssertion(OAuth2AuthorizationContext context) {
// @formatter:off
return Mono.just(context)
.map((ctx) -> ctx.getPrincipal().getPrincipal())
.filter((principal) -> principal instanceof Jwt)
.cast(Jwt.class);
.flatMap((ctx) -> {
Object principal = ctx.getPrincipal().getPrincipal();
return (principal instanceof Jwt)
? Mono.just((Jwt) principal)
: Mono.empty();
});
// @formatter:on
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**

25
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java

@ -22,7 +22,8 @@ import java.util.LinkedHashMap; @@ -22,7 +22,8 @@ import java.util.LinkedHashMap;
import java.util.Map;
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.client.registration.ClientRegistration;
import org.springframework.util.Assert;
@ -48,13 +49,13 @@ public final class OAuth2AuthorizationContext { @@ -48,13 +49,13 @@ public final class OAuth2AuthorizationContext {
public static final String REQUEST_SCOPE_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName()
.concat(".REQUEST_SCOPE");
private ClientRegistration clientRegistration;
private @Nullable ClientRegistration clientRegistration;
private OAuth2AuthorizedClient authorizedClient;
private @Nullable OAuth2AuthorizedClient authorizedClient;
private Authentication principal;
private @Nullable Authentication principal;
private Map<String, Object> attributes;
private @Nullable Map<String, Object> attributes;
private OAuth2AuthorizationContext() {
}
@ -64,6 +65,7 @@ public final class OAuth2AuthorizationContext { @@ -64,6 +65,7 @@ public final class OAuth2AuthorizationContext {
* @return the {@link ClientRegistration}
*/
public ClientRegistration getClientRegistration() {
Assert.notNull(this.clientRegistration, "clientRegistration cannot be null");
return this.clientRegistration;
}
@ -74,8 +76,7 @@ public final class OAuth2AuthorizationContext { @@ -74,8 +76,7 @@ public final class OAuth2AuthorizationContext {
* @return the {@link OAuth2AuthorizedClient} or {@code null} if the client
* registration was supplied
*/
@Nullable
public OAuth2AuthorizedClient getAuthorizedClient() {
public @Nullable OAuth2AuthorizedClient getAuthorizedClient() {
return this.authorizedClient;
}
@ -84,6 +85,7 @@ public final class OAuth2AuthorizationContext { @@ -84,6 +85,7 @@ public final class OAuth2AuthorizationContext {
* @return the {@code Principal} (to be) associated to the authorized client
*/
public Authentication getPrincipal() {
Assert.notNull(this.principal, "principal cannot be null");
return this.principal;
}
@ -92,6 +94,7 @@ public final class OAuth2AuthorizationContext { @@ -92,6 +94,7 @@ public final class OAuth2AuthorizationContext {
* @return a {@code Map} of the attributes associated to the context
*/
public Map<String, Object> getAttributes() {
Assert.notNull(this.attributes, "attributes cannot be null");
return this.attributes;
}
@ -131,13 +134,13 @@ public final class OAuth2AuthorizationContext { @@ -131,13 +134,13 @@ public final class OAuth2AuthorizationContext {
*/
public static final class Builder {
private ClientRegistration clientRegistration;
private @Nullable ClientRegistration clientRegistration;
private OAuth2AuthorizedClient authorizedClient;
private @Nullable OAuth2AuthorizedClient authorizedClient;
private Authentication principal;
private @Nullable Authentication principal;
private Map<String, Object> attributes;
private @Nullable Map<String, Object> attributes;
private Builder(ClientRegistration clientRegistration) {
Assert.notNull(clientRegistration, "clientRegistration cannot be null");

28
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizeRequest.java

@ -23,7 +23,8 @@ import java.util.LinkedHashMap; @@ -23,7 +23,8 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
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.GrantedAuthority;
@ -43,13 +44,13 @@ import org.springframework.util.CollectionUtils; @@ -43,13 +44,13 @@ import org.springframework.util.CollectionUtils;
*/
public final class OAuth2AuthorizeRequest {
private String clientRegistrationId;
private @Nullable String clientRegistrationId;
private OAuth2AuthorizedClient authorizedClient;
private @Nullable OAuth2AuthorizedClient authorizedClient;
private Authentication principal;
private @Nullable Authentication principal;
private Map<String, Object> attributes;
private @Nullable Map<String, Object> attributes;
private OAuth2AuthorizeRequest() {
}
@ -59,6 +60,7 @@ public final class OAuth2AuthorizeRequest { @@ -59,6 +60,7 @@ public final class OAuth2AuthorizeRequest {
* @return the identifier for the client registration
*/
public String getClientRegistrationId() {
Assert.notNull(this.clientRegistrationId, "clientRegistrationId cannot be null");
return this.clientRegistrationId;
}
@ -67,8 +69,7 @@ public final class OAuth2AuthorizeRequest { @@ -67,8 +69,7 @@ public final class OAuth2AuthorizeRequest {
* was not provided.
* @return the {@link OAuth2AuthorizedClient} or {@code null} if it was not provided
*/
@Nullable
public OAuth2AuthorizedClient getAuthorizedClient() {
public @Nullable OAuth2AuthorizedClient getAuthorizedClient() {
return this.authorizedClient;
}
@ -77,6 +78,7 @@ public final class OAuth2AuthorizeRequest { @@ -77,6 +78,7 @@ public final class OAuth2AuthorizeRequest {
* @return the {@code Principal} (to be) associated to the authorized client
*/
public Authentication getPrincipal() {
Assert.notNull(this.principal, "principal cannot be null");
return this.principal;
}
@ -85,6 +87,7 @@ public final class OAuth2AuthorizeRequest { @@ -85,6 +87,7 @@ public final class OAuth2AuthorizeRequest {
* @return a {@code Map} of the attributes associated to the request
*/
public Map<String, Object> getAttributes() {
Assert.notNull(this.attributes, "attributes cannot be null");
return this.attributes;
}
@ -95,9 +98,8 @@ public final class OAuth2AuthorizeRequest { @@ -95,9 +98,8 @@ public final class OAuth2AuthorizeRequest {
* @param <T> the type of the attribute
* @return the value of the attribute associated to the request
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getAttribute(String name) {
public <T> @Nullable T getAttribute(String name) {
return (T) this.getAttributes().get(name);
}
@ -127,13 +129,13 @@ public final class OAuth2AuthorizeRequest { @@ -127,13 +129,13 @@ public final class OAuth2AuthorizeRequest {
*/
public static final class Builder {
private String clientRegistrationId;
private @Nullable String clientRegistrationId;
private OAuth2AuthorizedClient authorizedClient;
private @Nullable OAuth2AuthorizedClient authorizedClient;
private Authentication principal;
private @Nullable Authentication principal;
private Map<String, Object> attributes;
private @Nullable Map<String, Object> attributes;
private Builder(String clientRegistrationId) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java

@ -18,7 +18,8 @@ package org.springframework.security.oauth2.client; @@ -18,7 +18,8 @@ package org.springframework.security.oauth2.client;
import java.io.Serializable;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@ -50,7 +51,7 @@ public class OAuth2AuthorizedClient implements Serializable { @@ -50,7 +51,7 @@ public class OAuth2AuthorizedClient implements Serializable {
private final OAuth2AccessToken accessToken;
private final OAuth2RefreshToken refreshToken;
private final @Nullable OAuth2RefreshToken refreshToken;
/**
* Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.java

@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
package org.springframework.security.oauth2.client;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
@ -62,7 +63,6 @@ public interface OAuth2AuthorizedClientManager { @@ -62,7 +63,6 @@ public interface OAuth2AuthorizedClientManager {
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
* supported for the specified client
*/
@Nullable
OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest);
@Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest);
}

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProvider.java

@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
package org.springframework.security.oauth2.client;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -46,7 +47,6 @@ public interface OAuth2AuthorizedClientProvider { @@ -46,7 +47,6 @@ public interface OAuth2AuthorizedClientProvider {
* @return the {@link OAuth2AuthorizedClient} or {@code null} if authorization is not
* supported for the specified client
*/
@Nullable
OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context);
@Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context);
}

16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java

@ -25,6 +25,8 @@ import java.util.List; @@ -25,6 +25,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
@ -157,11 +159,11 @@ public final class OAuth2AuthorizedClientProviderBuilder { @@ -157,11 +159,11 @@ public final class OAuth2AuthorizedClientProviderBuilder {
*/
public final class ClientCredentialsGrantBuilder implements Builder {
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
private @Nullable OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
private Duration clockSkew;
private @Nullable Duration clockSkew;
private Clock clock;
private @Nullable Clock clock;
private ClientCredentialsGrantBuilder() {
}
@ -249,13 +251,13 @@ public final class OAuth2AuthorizedClientProviderBuilder { @@ -249,13 +251,13 @@ public final class OAuth2AuthorizedClientProviderBuilder {
*/
public final class RefreshTokenGrantBuilder implements Builder {
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
private @Nullable OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
private ApplicationEventPublisher eventPublisher;
private @Nullable ApplicationEventPublisher eventPublisher;
private Duration clockSkew;
private @Nullable Duration clockSkew;
private Clock clock;
private @Nullable Clock clock;
private RefreshTokenGrantBuilder() {
}

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -46,7 +48,8 @@ public interface OAuth2AuthorizedClientService { @@ -46,7 +48,8 @@ public interface OAuth2AuthorizedClientService {
* @param <T> a type of OAuth2AuthorizedClient
* @return the {@link OAuth2AuthorizedClient} or {@code null} if not available
*/
<T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName);
<T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
String principalName);
/**
* Saves the {@link OAuth2AuthorizedClient} associating it to the provided End-User

68
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/R2dbcReactiveOAuth2AuthorizedClientService.java

@ -31,6 +31,7 @@ import java.util.function.Function; @@ -31,6 +31,7 @@ import java.util.function.Function;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.dao.DataRetrievalFailureException;
@ -240,7 +241,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -240,7 +241,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
private final OAuth2AccessToken accessToken;
private final OAuth2RefreshToken refreshToken;
private final @Nullable OAuth2RefreshToken refreshToken;
/**
* Constructs an {@code OAuth2AuthorizedClientHolder} using the provided
@ -266,7 +267,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -266,7 +267,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
* @param refreshToken the refresh token
*/
public OAuth2AuthorizedClientHolder(String clientRegistrationId, String principalName,
OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) {
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
Assert.notNull(accessToken, "accessToken cannot be null");
@ -288,7 +289,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -288,7 +289,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
return this.accessToken;
}
public OAuth2RefreshToken getRefreshToken() {
public @Nullable OAuth2RefreshToken getRefreshToken() {
return this.refreshToken;
}
@ -317,10 +318,16 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -317,10 +318,16 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
Parameter.fromOrEmpty(accessToken.getTokenType().getValue(), String.class));
parameters.put("accessTokenValue", Parameter.fromOrEmpty(
ByteBuffer.wrap(accessToken.getTokenValue().getBytes(StandardCharsets.UTF_8)), ByteBuffer.class));
parameters.put("accessTokenIssuedAt", Parameter
.fromOrEmpty(LocalDateTime.ofInstant(accessToken.getIssuedAt(), ZoneOffset.UTC), LocalDateTime.class));
parameters.put("accessTokenExpiresAt", Parameter
.fromOrEmpty(LocalDateTime.ofInstant(accessToken.getExpiresAt(), ZoneOffset.UTC), LocalDateTime.class));
Instant accessTokenIssuedAt = accessToken.getIssuedAt();
Instant accessTokenExpiresAt = accessToken.getExpiresAt();
parameters.put("accessTokenIssuedAt", Parameter.fromOrEmpty(
(accessTokenIssuedAt != null) ? LocalDateTime.ofInstant(accessTokenIssuedAt, ZoneOffset.UTC) : null,
LocalDateTime.class));
parameters.put("accessTokenExpiresAt",
Parameter.fromOrEmpty(
(accessTokenExpiresAt != null)
? LocalDateTime.ofInstant(accessTokenExpiresAt, ZoneOffset.UTC) : null,
LocalDateTime.class));
String accessTokenScopes = null;
if (!CollectionUtils.isEmpty(accessToken.getScopes())) {
accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getScopes(), ",");
@ -353,17 +360,29 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -353,17 +360,29 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
@Override
public OAuth2AuthorizedClientHolder apply(Row row, RowMetadata rowMetadata) {
String dbClientRegistrationId = row.get("client_registration_id", String.class);
OAuth2AccessToken.TokenType tokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue()
.equalsIgnoreCase(row.get("access_token_type", String.class))) {
tokenType = OAuth2AccessToken.TokenType.BEARER;
String clientRegistrationId = row.get("client_registration_id", String.class);
Assert.hasText(clientRegistrationId, "client_registration_id cannot be empty");
String principalName = row.get("principal_name", String.class);
Assert.hasText(principalName, "principal_name cannot be empty");
OAuth2AccessToken.TokenType tokenType = OAuth2AccessToken.TokenType.BEARER;
String accessTokenType = row.get("access_token_type", String.class);
if (StringUtils.hasText(accessTokenType)
&& !OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessTokenType)) {
tokenType = new OAuth2AccessToken.TokenType(accessTokenType);
}
ByteBuffer accessTokenValueBuffer = row.get("access_token_value", ByteBuffer.class);
Assert.notNull(accessTokenValueBuffer, "access_token_value cannot be null");
String tokenValue = new String(accessTokenValueBuffer.array(), StandardCharsets.UTF_8);
Instant issuedAt = null;
LocalDateTime issuedAtLdt = row.get("access_token_issued_at", LocalDateTime.class);
if (issuedAtLdt != null) {
issuedAt = issuedAtLdt.toInstant(ZoneOffset.UTC);
}
Instant expiresAt = null;
LocalDateTime expiresAtLdt = row.get("access_token_expires_at", LocalDateTime.class);
if (expiresAtLdt != null) {
expiresAt = expiresAtLdt.toInstant(ZoneOffset.UTC);
}
String tokenValue = new String(row.get("access_token_value", ByteBuffer.class).array(),
StandardCharsets.UTF_8);
Instant issuedAt = row.get("access_token_issued_at", LocalDateTime.class).toInstant(ZoneOffset.UTC);
Instant expiresAt = row.get("access_token_expires_at", LocalDateTime.class).toInstant(ZoneOffset.UTC);
Set<String> scopes = Collections.emptySet();
String accessTokenScopes = row.get("access_token_scopes", String.class);
@ -374,19 +393,18 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth @@ -374,19 +393,18 @@ public class R2dbcReactiveOAuth2AuthorizedClientService implements ReactiveOAuth
scopes);
OAuth2RefreshToken refreshToken = null;
ByteBuffer refreshTokenValue = row.get("refresh_token_value", ByteBuffer.class);
if (refreshTokenValue != null) {
tokenValue = new String(refreshTokenValue.array(), StandardCharsets.UTF_8);
ByteBuffer refreshTokenValueBuffer = row.get("refresh_token_value", ByteBuffer.class);
if (refreshTokenValueBuffer != null) {
tokenValue = new String(refreshTokenValueBuffer.array(), StandardCharsets.UTF_8);
issuedAt = null;
LocalDateTime refreshTokenIssuedAt = row.get("refresh_token_issued_at", LocalDateTime.class);
if (refreshTokenIssuedAt != null) {
issuedAt = refreshTokenIssuedAt.toInstant(ZoneOffset.UTC);
issuedAtLdt = row.get("refresh_token_issued_at", LocalDateTime.class);
if (issuedAtLdt != null) {
issuedAt = issuedAtLdt.toInstant(ZoneOffset.UTC);
}
refreshToken = new OAuth2RefreshToken(tokenValue, issuedAt);
}
String dbPrincipalName = row.get("principal_name", String.class);
return new OAuth2AuthorizedClientHolder(dbClientRegistrationId, dbPrincipalName, accessToken, refreshToken);
return new OAuth2AuthorizedClientHolder(clientRegistrationId, principalName, accessToken, refreshToken);
}
}

16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java

@ -25,6 +25,8 @@ import java.util.Map; @@ -25,6 +25,8 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
@ -178,11 +180,11 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { @@ -178,11 +180,11 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
*/
public final class ClientCredentialsGrantBuilder implements Builder {
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
private @Nullable ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
private Duration clockSkew;
private @Nullable Duration clockSkew;
private Clock clock;
private @Nullable Clock clock;
private ClientCredentialsGrantBuilder() {
}
@ -252,13 +254,13 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { @@ -252,13 +254,13 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder {
*/
public final class RefreshTokenGrantBuilder implements Builder {
private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
private @Nullable ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient;
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private @Nullable ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private Duration clockSkew;
private @Nullable Duration clockSkew;
private Clock clock;
private @Nullable Clock clock;
private RefreshTokenGrantBuilder() {
}

27
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler.java

@ -16,13 +16,15 @@ @@ -16,13 +16,15 @@
package org.springframework.security.oauth2.client;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
@ -191,7 +193,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler @@ -191,7 +193,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
this.clockSkew = clockSkew;
}
private String extractIdToken(Map<String, Object> attributes) {
private @Nullable String extractIdToken(Map<String, Object> attributes) {
if (attributes.get(OidcParameterNames.ID_TOKEN) instanceof String idToken) {
return idToken;
}
@ -224,7 +226,10 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler @@ -224,7 +226,10 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
}
private void validateIssuer(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getIssuer().toString().equals(existingOidcUser.getIdToken().getIssuer().toString())) {
URL idTokenIssuer = idToken.getIssuer();
URL existingIdTokenIssuer = existingOidcUser.getIdToken().getIssuer();
if (idTokenIssuer == null || existingIdTokenIssuer == null
|| !idTokenIssuer.toString().equals(existingIdTokenIssuer.toString())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issuer",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -232,7 +237,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler @@ -232,7 +237,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
}
private void validateSubject(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getSubject().equals(existingOidcUser.getIdToken().getSubject())) {
if (!Objects.equals(idToken.getSubject(), existingOidcUser.getIdToken().getSubject())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid subject",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -240,7 +245,10 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler @@ -240,7 +245,10 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
}
private void validateIssuedAt(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getIssuedAt().isAfter(existingOidcUser.getIdToken().getIssuedAt().minus(this.clockSkew))) {
Instant idTokenIssuedAt = idToken.getIssuedAt();
Instant existingIdTokenIssuedAt = existingOidcUser.getIdToken().getIssuedAt();
if (idTokenIssuedAt == null || existingIdTokenIssuedAt == null
|| !idTokenIssuedAt.isAfter(existingIdTokenIssuedAt.minus(this.clockSkew))) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issued at time",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -257,12 +265,13 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler @@ -257,12 +265,13 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
private boolean isValidAudience(OidcUser existingOidcUser, OidcIdToken idToken) {
List<String> idTokenAudiences = idToken.getAudience();
Set<String> oidcUserAudiences = new HashSet<>(existingOidcUser.getIdToken().getAudience());
if (idTokenAudiences.size() != oidcUserAudiences.size()) {
List<String> existingIdTokenAudiences = existingOidcUser.getIdToken().getAudience();
if (idTokenAudiences == null || existingIdTokenAudiences == null
|| idTokenAudiences.size() != existingIdTokenAudiences.size()) {
return false;
}
for (String audience : idTokenAudiences) {
if (!oidcUserAudiences.contains(audience)) {
if (!existingIdTokenAudiences.contains(audience)) {
return false;
}
}

11
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java

@ -24,9 +24,10 @@ import java.util.Collections; @@ -24,9 +24,10 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.endpoint.RestClientRefreshTokenTokenResponseClient;
@ -51,7 +52,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider @@ -51,7 +52,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider
private OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new RestClientRefreshTokenTokenResponseClient();
private ApplicationEventPublisher applicationEventPublisher;
private @Nullable ApplicationEventPublisher applicationEventPublisher;
private Duration clockSkew = Duration.ofSeconds(60);
@ -78,8 +79,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider @@ -78,8 +79,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider
* not supported
*/
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (authorizedClient == null || authorizedClient.getRefreshToken() == null
@ -123,7 +123,8 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider @@ -123,7 +123,8 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java

@ -180,7 +180,8 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider @@ -180,7 +180,8 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
}

20
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java

@ -21,7 +21,8 @@ import java.time.Duration; @@ -21,7 +21,8 @@ import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.RestClientTokenExchangeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest;
@ -45,9 +46,9 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -45,9 +46,9 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
private OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> accessTokenResponseClient = new RestClientTokenExchangeTokenResponseClient();
private Function<OAuth2AuthorizationContext, OAuth2Token> subjectTokenResolver = this::resolveSubjectToken;
private Function<OAuth2AuthorizationContext, @Nullable OAuth2Token> subjectTokenResolver = this::resolveSubjectToken;
private Function<OAuth2AuthorizationContext, OAuth2Token> actorTokenResolver = (context) -> null;
private Function<OAuth2AuthorizationContext, @Nullable OAuth2Token> actorTokenResolver = (context) -> null;
private Duration clockSkew = Duration.ofSeconds(60);
@ -66,8 +67,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -66,8 +67,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
* supported
*/
@Override
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
if (!AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType())) {
@ -93,7 +93,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -93,7 +93,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
}
private OAuth2Token resolveSubjectToken(OAuth2AuthorizationContext context) {
private @Nullable OAuth2Token resolveSubjectToken(OAuth2AuthorizationContext context) {
if (context.getPrincipal().getPrincipal() instanceof OAuth2Token accessToken) {
return accessToken;
}
@ -111,7 +111,8 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -111,7 +111,8 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**
@ -131,7 +132,8 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -131,7 +132,8 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
* @param subjectTokenResolver the resolver used for resolving the {@link OAuth2Token
* subject token}
*/
public void setSubjectTokenResolver(Function<OAuth2AuthorizationContext, OAuth2Token> subjectTokenResolver) {
public void setSubjectTokenResolver(
Function<OAuth2AuthorizationContext, @Nullable OAuth2Token> subjectTokenResolver) {
Assert.notNull(subjectTokenResolver, "subjectTokenResolver cannot be null");
this.subjectTokenResolver = subjectTokenResolver;
}
@ -141,7 +143,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2 @@ -141,7 +143,7 @@ public final class TokenExchangeOAuth2AuthorizedClientProvider implements OAuth2
* @param actorTokenResolver the resolver used for resolving the {@link OAuth2Token
* actor token}
*/
public void setActorTokenResolver(Function<OAuth2AuthorizationContext, OAuth2Token> actorTokenResolver) {
public void setActorTokenResolver(Function<OAuth2AuthorizationContext, @Nullable OAuth2Token> actorTokenResolver) {
Assert.notNull(actorTokenResolver, "actorTokenResolver cannot be null");
this.actorTokenResolver = actorTokenResolver;
}

8
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeReactiveOAuth2AuthorizedClientProvider.java

@ -83,7 +83,7 @@ public final class TokenExchangeReactiveOAuth2AuthorizedClientProvider @@ -83,7 +83,7 @@ public final class TokenExchangeReactiveOAuth2AuthorizedClientProvider
return this.subjectTokenResolver.apply(context)
.flatMap((subjectToken) -> this.actorTokenResolver.apply(context)
.map((actorToken) -> new TokenExchangeGrantRequest(clientRegistration, subjectToken, actorToken))
.defaultIfEmpty(new TokenExchangeGrantRequest(clientRegistration, subjectToken, null)))
.switchIfEmpty(Mono.just(new TokenExchangeGrantRequest(clientRegistration, subjectToken, null))))
.flatMap(this.accessTokenResponseClient::getTokenResponse)
.onErrorMap(OAuth2AuthorizationException.class,
(ex) -> new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex))
@ -94,14 +94,16 @@ public final class TokenExchangeReactiveOAuth2AuthorizedClientProvider @@ -94,14 +94,16 @@ public final class TokenExchangeReactiveOAuth2AuthorizedClientProvider
private Mono<OAuth2Token> resolveSubjectToken(OAuth2AuthorizationContext context) {
// @formatter:off
return Mono.just(context)
.map((ctx) -> ctx.getPrincipal().getPrincipal())
.flatMap((ctx) -> Mono.justOrEmpty(ctx.getPrincipal())
.flatMap((auth) -> Mono.justOrEmpty(auth.getPrincipal())))
.filter((principal) -> principal instanceof OAuth2Token)
.cast(OAuth2Token.class);
// @formatter:on
}
private boolean hasTokenExpired(OAuth2Token token) {
return this.clock.instant().isAfter(token.getExpiresAt().minus(this.clockSkew));
Instant expiresAt = token.getExpiresAt();
return expiresAt != null && this.clock.instant().isAfter(expiresAt.minus(this.clockSkew));
}
/**

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/annotation/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Annotations for OAuth2 Client (e.g. method parameters).
*/
@NullMarked
package org.springframework.security.oauth2.client.annotation;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/aot/hint/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Runtime hints for OAuth2 Client AOT support.
*/
@NullMarked
package org.springframework.security.oauth2.client.aot.hint;
import org.jspecify.annotations.NullMarked;

8
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.authentication;
import java.util.Objects;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -74,11 +76,13 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica @@ -74,11 +76,13 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthorizationException(authorizationResponse.getError());
OAuth2Error error = authorizationResponse.getError();
Assert.notNull(error, "error cannot be null when status is error");
throw new OAuth2AuthorizationException(error);
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
if (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthorizationException(oauth2Error);
}

13
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationToken.java

@ -20,7 +20,8 @@ import java.util.Collections; @@ -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.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -50,9 +51,9 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti @@ -50,9 +51,9 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
private OAuth2AuthorizationExchange authorizationExchange;
private OAuth2AccessToken accessToken;
private @Nullable OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken;
private @Nullable OAuth2RefreshToken refreshToken;
/**
* This constructor should be used when the Authorization Request/Response is
@ -97,7 +98,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti @@ -97,7 +98,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
public OAuth2AuthorizationCodeAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange, OAuth2AccessToken accessToken,
OAuth2RefreshToken refreshToken, Map<String, Object> additionalParameters) {
@Nullable OAuth2RefreshToken refreshToken, Map<String, Object> additionalParameters) {
this(clientRegistration, authorizationExchange);
Assert.notNull(accessToken, "accessToken cannot be null");
this.accessToken = accessToken;
@ -112,7 +113,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti @@ -112,7 +113,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
}
@Override
public Object getCredentials() {
public @Nullable Object getCredentials() {
return (this.accessToken != null) ? this.accessToken.getTokenValue()
: this.authorizationExchange.getAuthorizationResponse().getCode();
}
@ -137,7 +138,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti @@ -137,7 +138,7 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
* Returns the {@link OAuth2AccessToken access token}.
* @return the {@link OAuth2AccessToken}
*/
public OAuth2AccessToken getAccessToken() {
public @Nullable OAuth2AccessToken getAccessToken() {
return this.accessToken;
}

7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client.authentication;
import java.util.Objects;
import java.util.function.Function;
import reactor.core.publisher.Mono;
@ -87,11 +88,13 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements Rea @@ -87,11 +88,13 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements Rea
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
OAuth2Error error = authorizationResponse.getError();
Assert.notNull(error, "error cannot be null when status is error");
return Mono.error(new OAuth2AuthorizationException(error));
}
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange()
.getAuthorizationRequest();
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
if (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
return Mono.error(new OAuth2AuthorizationException(oauth2Error));
}

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java

@ -21,6 +21,8 @@ import java.util.HashSet; @@ -21,6 +21,8 @@ import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -95,7 +97,7 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider @@ -95,7 +97,7 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
@ -120,9 +122,11 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider @@ -120,9 +122,11 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
Assert.notNull(accessToken, "accessToken cannot be null");
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
Assert.notNull(oauth2User, "oauth2User cannot be null");
Collection<GrantedAuthority> authorities = new HashSet<>(oauth2User.getAuthorities());
Collection<GrantedAuthority> mappedAuthorities = new LinkedHashSet<>(
this.authoritiesMapper.mapAuthorities(authorities));

13
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationToken.java

@ -19,7 +19,8 @@ package org.springframework.security.oauth2.client.authentication; @@ -19,7 +19,8 @@ package org.springframework.security.oauth2.client.authentication;
import java.util.Collection;
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.GrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@ -47,15 +48,15 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken @@ -47,15 +48,15 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
private static final long serialVersionUID = 620L;
private OAuth2User principal;
private @Nullable OAuth2User principal;
private ClientRegistration clientRegistration;
private OAuth2AuthorizationExchange authorizationExchange;
private OAuth2AccessToken accessToken;
private @Nullable OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken;
private @Nullable OAuth2RefreshToken refreshToken;
/**
* This constructor should be used when the Authorization Request/Response is
@ -118,7 +119,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken @@ -118,7 +119,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
}
@Override
public OAuth2User getPrincipal() {
public @Nullable OAuth2User getPrincipal() {
return this.principal;
}
@ -147,7 +148,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken @@ -147,7 +148,7 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
* Returns the {@link OAuth2AccessToken access token}.
* @return the {@link OAuth2AccessToken}
*/
public OAuth2AccessToken getAccessToken() {
public @Nullable OAuth2AccessToken getAccessToken() {
return this.accessToken;
}

1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java

@ -120,6 +120,7 @@ public class OAuth2LoginReactiveAuthenticationManager implements ReactiveAuthent @@ -120,6 +120,7 @@ public class OAuth2LoginReactiveAuthenticationManager implements ReactiveAuthent
private Mono<OAuth2LoginAuthenticationToken> onSuccess(OAuth2AuthorizationCodeAuthenticationToken authentication) {
OAuth2AccessToken accessToken = authentication.getAccessToken();
Assert.notNull(accessToken, "accessToken cannot be null");
Map<String, Object> additionalParameters = authentication.getAdditionalParameters();
OAuth2UserRequest userRequest = new OAuth2UserRequest(authentication.getClientRegistration(), accessToken,
additionalParameters);

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Support classes and interfaces for authenticating and authorizing a client with an
* OAuth 2.0 Authorization Server using a specific authorization grant flow.
*/
@NullMarked
package org.springframework.security.oauth2.client.authentication;
import org.jspecify.annotations.NullMarked;

6
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/DefaultOAuth2TokenRequestParametersConverter.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.endpoint;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@ -58,7 +60,7 @@ import org.springframework.util.MultiValueMap; @@ -58,7 +60,7 @@ import org.springframework.util.MultiValueMap;
public final class DefaultOAuth2TokenRequestParametersConverter<T extends AbstractOAuth2AuthorizationGrantRequest>
implements Converter<T, MultiValueMap<String, String>> {
private final Converter<T, MultiValueMap<String, String>> defaultParametersConverter = createDefaultParametersConverter();
private final Converter<T, @Nullable MultiValueMap<String, String>> defaultParametersConverter = createDefaultParametersConverter();
@Override
public MultiValueMap<String, String> convert(T grantRequest) {
@ -81,7 +83,7 @@ public final class DefaultOAuth2TokenRequestParametersConverter<T extends Abstra @@ -81,7 +83,7 @@ public final class DefaultOAuth2TokenRequestParametersConverter<T extends Abstra
return parameters;
}
private static <T extends AbstractOAuth2AuthorizationGrantRequest> Converter<T, MultiValueMap<String, String>> createDefaultParametersConverter() {
private static <T extends AbstractOAuth2AuthorizationGrantRequest> Converter<T, @Nullable MultiValueMap<String, String>> createDefaultParametersConverter() {
return (grantRequest) -> {
if (grantRequest instanceof OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
return OAuth2AuthorizationCodeGrantRequest.defaultParameters(authorizationCodeGrantRequest);

7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/NimbusJwtClientAuthenticationParametersConverter.java

@ -31,6 +31,7 @@ import com.nimbusds.jose.jwk.KeyType; @@ -31,6 +31,7 @@ import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@ -77,7 +78,7 @@ import org.springframework.util.MultiValueMap; @@ -77,7 +78,7 @@ import org.springframework.util.MultiValueMap;
* JOSE + JWT SDK</a>
*/
public final class NimbusJwtClientAuthenticationParametersConverter<T extends AbstractOAuth2AuthorizationGrantRequest>
implements Converter<T, MultiValueMap<String, String>> {
implements Converter<T, @Nullable MultiValueMap<String, String>> {
private static final String INVALID_KEY_ERROR_CODE = "invalid_key";
@ -104,7 +105,7 @@ public final class NimbusJwtClientAuthenticationParametersConverter<T extends Ab @@ -104,7 +105,7 @@ public final class NimbusJwtClientAuthenticationParametersConverter<T extends Ab
}
@Override
public MultiValueMap<String, String> convert(T authorizationGrantRequest) {
public @Nullable MultiValueMap<String, String> convert(T authorizationGrantRequest) {
Assert.notNull(authorizationGrantRequest, "authorizationGrantRequest cannot be null");
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
@ -173,7 +174,7 @@ public final class NimbusJwtClientAuthenticationParametersConverter<T extends Ab @@ -173,7 +174,7 @@ public final class NimbusJwtClientAuthenticationParametersConverter<T extends Ab
return parameters;
}
private static JwsAlgorithm resolveAlgorithm(JWK jwk) {
private static @Nullable JwsAlgorithm resolveAlgorithm(JWK jwk) {
JwsAlgorithm jwsAlgorithm = null;
if (jwk.getAlgorithm() != null) {

10
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.endpoint;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2Token;
@ -51,7 +53,7 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR @@ -51,7 +53,7 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR
private final OAuth2Token subjectToken;
private final OAuth2Token actorToken;
private final @Nullable OAuth2Token actorToken;
/**
* Constructs a {@code TokenExchangeGrantRequest} using the provided parameters.
@ -60,7 +62,7 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR @@ -60,7 +62,7 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR
* @param actorToken the actor token
*/
public TokenExchangeGrantRequest(ClientRegistration clientRegistration, OAuth2Token subjectToken,
OAuth2Token actorToken) {
@Nullable OAuth2Token actorToken) {
super(AuthorizationGrantType.TOKEN_EXCHANGE, clientRegistration);
Assert.isTrue(AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType()),
"clientRegistration.authorizationGrantType must be AuthorizationGrantType.TOKEN_EXCHANGE");
@ -79,9 +81,9 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR @@ -79,9 +81,9 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR
/**
* Returns the {@link OAuth2Token actor token}.
* @return the {@link OAuth2Token actor token}
* @return the {@link OAuth2Token actor token}, or {@code null} if not present
*/
public OAuth2Token getActorToken() {
public @Nullable OAuth2Token getActorToken() {
return this.actorToken;
}

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Classes and interfaces providing support to the client for initiating requests to the
* Authorization Server's Protocol Endpoints.
*/
@NullMarked
package org.springframework.security.oauth2.client.endpoint;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/event/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Events for OAuth2 Client lifecycle.
*/
@NullMarked
package org.springframework.security.oauth2.client.event;
import org.jspecify.annotations.NullMarked;

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java

@ -20,6 +20,7 @@ import java.io.IOException; @@ -20,6 +20,7 @@ import java.io.IOException;
import java.net.URI;
import com.nimbusds.oauth2.sdk.token.BearerTokenError;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@ -68,7 +69,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler { @@ -68,7 +69,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
throw new OAuth2AuthorizationException(oauth2Error);
}
private OAuth2Error readErrorFromWwwAuthenticate(HttpHeaders headers) {
private @Nullable OAuth2Error readErrorFromWwwAuthenticate(HttpHeaders headers) {
String wwwAuthenticateHeader = headers.getFirst(HttpHeaders.WWW_AUTHENTICATE);
if (!StringUtils.hasText(wwwAuthenticateHeader)) {
return null;
@ -84,7 +85,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler { @@ -84,7 +85,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
return new OAuth2Error(errorCode, errorDescription, errorUri);
}
private BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
private @Nullable BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
try {
return BearerTokenError.parse(wwwAuthenticateHeader);
}

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/package-info.java

@ -0,0 +1,23 @@ @@ -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 client support for OAuth2 Client.
*/
@NullMarked
package org.springframework.security.oauth2.client.http;
import org.jspecify.annotations.NullMarked;

53
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/ClientRegistrationDeserializer.java

@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
package org.springframework.security.oauth2.client.jackson;
import java.util.Map;
import java.util.Set;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
@ -26,6 +29,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio @@ -26,6 +29,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.Assert;
/**
* A {@code JsonDeserializer} for {@link ClientRegistration}.
@ -49,28 +53,45 @@ final class ClientRegistrationDeserializer extends ValueDeserializer<ClientRegis @@ -49,28 +53,45 @@ final class ClientRegistrationDeserializer extends ValueDeserializer<ClientRegis
JsonNode clientRegistrationNode = context.readTree(parser);
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
return ClientRegistration
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
.clientSecret(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret"))
String registrationId = JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId");
Assert.hasText(registrationId, "registrationId cannot be null or empty");
String clientId = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId");
Assert.hasText(clientId, "clientId cannot be null or empty");
String clientSecret = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret");
String redirectUri = JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri");
Set<String> scopes = JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET,
context);
String clientName = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName");
String authorizationUri = JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri");
String tokenUri = JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri");
Assert.hasText(tokenUri, "tokenUri cannot be null or empty");
String userInfoUri = JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri");
String userNameAttributeName = JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName");
String jwkSetUri = JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri");
String issuerUri = JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri");
Map<String, Object> configurationMetadata = JsonNodeUtils.findValue(providerDetailsNode,
"configurationMetadata", JsonNodeUtils.STRING_OBJECT_MAP, context);
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId)
.clientId(clientId)
.clientSecret(clientSecret)
.clientAuthenticationMethod(CLIENT_AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, context))
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
.userInfoUri(JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri"))
.redirectUri(redirectUri)
.scope(scopes)
.clientName(clientName)
.authorizationUri(authorizationUri)
.tokenUri(tokenUri)
.userInfoUri(userInfoUri)
.userInfoAuthenticationMethod(AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(userInfoEndpointNode, "authenticationMethod")))
.userNameAttributeName(JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName"))
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
JsonNodeUtils.STRING_OBJECT_MAP, context))
.build();
.userNameAttributeName(userNameAttributeName)
.jwkSetUri(jwkSetUri)
.issuerUri(issuerUri)
.providerConfigurationMetadata(
(configurationMetadata != null) ? configurationMetadata : java.util.Collections.emptyMap());
return builder.build();
}
}

7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/JsonNodeUtils.java

@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.jackson; @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.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;
@ -38,7 +39,7 @@ abstract class JsonNodeUtils { @@ -38,7 +39,7 @@ abstract class JsonNodeUtils {
static final TypeReference<Map<String, Object>> 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;
}
@ -46,7 +47,7 @@ abstract class JsonNodeUtils { @@ -46,7 +47,7 @@ abstract class JsonNodeUtils {
return (value != null && value.isString()) ? value.stringValue() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
static <T> @Nullable T findValue(@Nullable JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
DeserializationContext context) {
if (jsonNode == null) {
return null;
@ -56,7 +57,7 @@ abstract class JsonNodeUtils { @@ -56,7 +57,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;
}

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/OAuth2AuthorizationRequestDeserializer.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.jackson;
import java.util.Map;
import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.DeserializationContext;
@ -26,6 +28,7 @@ import tools.jackson.databind.util.StdConverter; @@ -26,6 +28,7 @@ import tools.jackson.databind.util.StdConverter;
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}.
@ -50,15 +53,25 @@ final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAu @@ -50,15 +53,25 @@ final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAu
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
String authorizationUri = JsonNodeUtils.findStringValue(root, "authorizationUri");
Assert.hasText(authorizationUri, "authorizationUri cannot be null or empty");
builder.authorizationUri(authorizationUri);
String clientId = JsonNodeUtils.findStringValue(root, "clientId");
Assert.hasText(clientId, "clientId cannot be null or empty");
builder.clientId(clientId);
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, context));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
Map<String, Object> additionalParameters = JsonNodeUtils.findValue(root, "additionalParameters",
JsonNodeUtils.STRING_OBJECT_MAP, context);
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, context));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, context));
(additionalParameters != null) ? additionalParameters : java.util.Collections.emptyMap());
String authorizationRequestUri = JsonNodeUtils.findStringValue(root, "authorizationRequestUri");
Assert.hasText(authorizationRequestUri, "authorizationRequestUri cannot be null or empty");
builder.authorizationRequestUri(authorizationRequestUri);
Map<String, Object> attributes = JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP,
context);
builder.attributes((attributes != null) ? attributes : java.util.Collections.emptyMap());
return builder.build();
}

16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/StdConverters.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client.jackson;
import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.util.StdConverter;
@ -23,6 +24,7 @@ import org.springframework.security.oauth2.core.AuthenticationMethod; @@ -23,6 +24,7 @@ import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.util.Assert;
/**
* {@code StdConverter} implementations.
@ -36,9 +38,9 @@ abstract class StdConverters { @@ -36,9 +38,9 @@ abstract class StdConverters {
static final class AccessTokenTypeConverter extends StdConverter<JsonNode, OAuth2AccessToken.TokenType> {
@Override
public OAuth2AccessToken.TokenType convert(JsonNode jsonNode) {
public OAuth2AccessToken.@Nullable TokenType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
if (value != null && OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
return OAuth2AccessToken.TokenType.BEARER;
}
return null;
@ -51,6 +53,7 @@ abstract class StdConverters { @@ -51,6 +53,7 @@ abstract class StdConverters {
@Override
public ClientAuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
Assert.hasText(value, "value cannot be null or empty");
return ClientAuthenticationMethod.valueOf(value);
}
@ -61,6 +64,7 @@ abstract class StdConverters { @@ -61,6 +64,7 @@ abstract class StdConverters {
@Override
public AuthorizationGrantType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
Assert.hasText(value, "value cannot be null or empty");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
@ -75,15 +79,15 @@ abstract class StdConverters { @@ -75,15 +79,15 @@ abstract class StdConverters {
static final class AuthenticationMethodConverter extends StdConverter<JsonNode, AuthenticationMethod> {
@Override
public AuthenticationMethod convert(JsonNode jsonNode) {
public @Nullable AuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.HEADER;
}
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.FORM;
}
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.QUERY;
}
return null;

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Jackson 3+ serialization support for OAuth2 client.
*/
@NullMarked
package org.springframework.security.oauth2.client.jackson;
import org.jspecify.annotations.NullMarked;

51
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package org.springframework.security.oauth2.client.jackson2;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
@ -29,6 +31,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio @@ -29,6 +31,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.Assert;
/**
* A {@code JsonDeserializer} for {@link ClientRegistration}.
@ -56,28 +59,44 @@ final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegist @@ -56,28 +59,44 @@ final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegist
JsonNode clientRegistrationNode = mapper.readTree(parser);
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
return ClientRegistration
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
.clientSecret(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret"))
String registrationId = JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId");
Assert.hasText(registrationId, "registrationId cannot be empty");
String clientId = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId");
Assert.hasText(clientId, "clientId cannot be empty");
String clientSecret = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret");
String redirectUri = JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri");
Set<String> scopes = JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET,
mapper);
String clientName = JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName");
String authorizationUri = JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri");
String tokenUri = JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri");
Assert.hasText(tokenUri, "tokenUri cannot be empty");
String userInfoUri = JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri");
String userNameAttributeName = JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName");
String jwkSetUri = JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri");
String issuerUri = JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri");
Map<String, Object> configurationMetadata = JsonNodeUtils.findValue(providerDetailsNode,
"configurationMetadata", JsonNodeUtils.STRING_OBJECT_MAP, mapper);
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId)
.clientId(clientId)
.clientSecret(clientSecret)
.clientAuthenticationMethod(CLIENT_AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, mapper))
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
.userInfoUri(JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri"))
.redirectUri(redirectUri)
.scope(scopes)
.clientName(clientName)
.authorizationUri(authorizationUri)
.tokenUri(tokenUri)
.userInfoUri(userInfoUri)
.userInfoAuthenticationMethod(AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(userInfoEndpointNode, "authenticationMethod")))
.userNameAttributeName(JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName"))
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
JsonNodeUtils.STRING_OBJECT_MAP, mapper))
.build();
.userNameAttributeName(userNameAttributeName)
.jwkSetUri(jwkSetUri)
.issuerUri(issuerUri)
.providerConfigurationMetadata(configurationMetadata);
return builder.build();
}
}

7
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/JsonNodeUtils.java

@ -22,6 +22,7 @@ import java.util.Set; @@ -22,6 +22,7 @@ import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable;
/**
* Utility class for {@code JsonNode}.
@ -41,7 +42,7 @@ abstract class JsonNodeUtils { @@ -41,7 +42,7 @@ abstract class JsonNodeUtils {
static final TypeReference<Map<String, Object>> 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 { @@ -49,7 +50,7 @@ abstract class JsonNodeUtils {
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
static <T> @Nullable T findValue(@Nullable JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
ObjectMapper mapper) {
if (jsonNode == null) {
return null;
@ -58,7 +59,7 @@ abstract class JsonNodeUtils { @@ -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;
}

25
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestDeserializer.java

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package org.springframework.security.oauth2.client.jackson2;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
@ -29,6 +31,7 @@ import com.fasterxml.jackson.databind.util.StdConverter; @@ -29,6 +31,7 @@ import com.fasterxml.jackson.databind.util.StdConverter;
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}.
@ -59,15 +62,25 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAut @@ -59,15 +62,25 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAut
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
String authorizationUri = JsonNodeUtils.findStringValue(root, "authorizationUri");
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
builder.authorizationUri(authorizationUri);
String clientId = JsonNodeUtils.findStringValue(root, "clientId");
Assert.hasText(clientId, "clientId cannot be empty");
builder.clientId(clientId);
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, mapper));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
Map<String, Object> additionalParameters = JsonNodeUtils.findValue(root, "additionalParameters",
JsonNodeUtils.STRING_OBJECT_MAP, mapper);
builder.additionalParameters((additionalParameters != null) ? additionalParameters : Collections.emptyMap());
String authorizationRequestUri = JsonNodeUtils.findStringValue(root, "authorizationRequestUri");
if (authorizationRequestUri != null) {
builder.authorizationRequestUri(authorizationRequestUri);
}
Map<String, Object> attributes = JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP,
mapper);
builder.attributes((attributes != null) ? attributes : Collections.emptyMap());
return builder.build();
}

16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/StdConverters.java

@ -18,11 +18,13 @@ package org.springframework.security.oauth2.client.jackson2; @@ -18,11 +18,13 @@ package org.springframework.security.oauth2.client.jackson2;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.util.StdConverter;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.util.Assert;
/**
* {@code StdConverter} implementations.
@ -39,9 +41,9 @@ abstract class StdConverters { @@ -39,9 +41,9 @@ abstract class StdConverters {
static final class AccessTokenTypeConverter extends StdConverter<JsonNode, OAuth2AccessToken.TokenType> {
@Override
public OAuth2AccessToken.TokenType convert(JsonNode jsonNode) {
public OAuth2AccessToken.@Nullable TokenType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
if (value != null && OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
return OAuth2AccessToken.TokenType.BEARER;
}
return null;
@ -54,6 +56,7 @@ abstract class StdConverters { @@ -54,6 +56,7 @@ abstract class StdConverters {
@Override
public ClientAuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
Assert.hasText(value, "value cannot be null or empty");
return ClientAuthenticationMethod.valueOf(value);
}
@ -64,6 +67,7 @@ abstract class StdConverters { @@ -64,6 +67,7 @@ abstract class StdConverters {
@Override
public AuthorizationGrantType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
Assert.hasText(value, "value cannot be null or empty");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
@ -78,15 +82,15 @@ abstract class StdConverters { @@ -78,15 +82,15 @@ abstract class StdConverters {
static final class AuthenticationMethodConverter extends StdConverter<JsonNode, AuthenticationMethod> {
@Override
public AuthenticationMethod convert(JsonNode jsonNode) {
public @Nullable AuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.HEADER;
}
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.FORM;
}
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
if (value != null && AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.QUERY;
}
return null;

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Jackson 2 serialization support for OAuth2 client.
*/
@NullMarked
package org.springframework.security.oauth2.client.jackson2;
import org.jspecify.annotations.NullMarked;

17
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java

@ -22,6 +22,9 @@ import java.security.NoSuchAlgorithmException; @@ -22,6 +22,9 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
@ -117,7 +120,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati @@ -117,7 +120,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
@ -136,10 +139,11 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati @@ -136,10 +139,11 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthenticationException(authorizationResponse.getError(),
authorizationResponse.getError().toString());
OAuth2Error error = authorizationResponse.getError();
Assert.notNull(error, "error cannot be null when status is error");
throw new OAuth2AuthenticationException(error, error.toString());
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
if (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
@ -157,6 +161,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati @@ -157,6 +161,7 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
validateNonce(authorizationRequest, idToken);
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
accessTokenResponse.getAccessToken(), idToken, additionalParameters));
Assert.notNull(oidcUser, "oidcUser cannot be null");
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oidcUser.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
@ -244,7 +249,9 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati @@ -244,7 +249,9 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
private Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {
try {
Map<String, Object> parameters = accessTokenResponse.getAdditionalParameters();
return jwtDecoder.decode((String) parameters.get(OidcParameterNames.ID_TOKEN));
String idToken = (String) parameters.get(OidcParameterNames.ID_TOKEN);
Assert.hasText(idToken, "id_token parameter cannot be null or empty");
return jwtDecoder.decode(idToken);
}
catch (JwtException ex) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);

9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java

@ -22,6 +22,7 @@ import java.security.NoSuchAlgorithmException; @@ -22,6 +22,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import reactor.core.publisher.Mono;
@ -132,10 +133,11 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React @@ -132,10 +133,11 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
return Mono.error(new OAuth2AuthenticationException(authorizationResponse.getError(),
authorizationResponse.getError().toString()));
OAuth2Error error = authorizationResponse.getError();
Assert.notNull(error, "error cannot be null when status is error");
return Mono.error(new OAuth2AuthenticationException(error, error.toString()));
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
if (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
return Mono.error(new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()));
}
@ -213,6 +215,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React @@ -213,6 +215,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
OAuth2AccessTokenResponse accessTokenResponse) {
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
Assert.hasText(rawIdToken, "id_token parameter cannot be null or empty");
// @formatter:off
return jwtDecoder.decode(rawIdToken)
.map((jwt) ->

40
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java

@ -16,12 +16,15 @@ @@ -16,12 +16,15 @@
package org.springframework.security.oauth2.client.oidc.authentication;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
@ -82,7 +85,7 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -82,7 +85,7 @@ public final class OidcAuthorizedClientRefreshedEventListener
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ApplicationEventPublisher applicationEventPublisher;
private @Nullable ApplicationEventPublisher applicationEventPublisher;
private Duration clockSkew = Duration.ofSeconds(60);
@ -131,6 +134,7 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -131,6 +134,7 @@ public final class OidcAuthorizedClientRefreshedEventListener
OidcUserRequest userRequest = new OidcUserRequest(clientRegistration, accessTokenResponse.getAccessToken(),
idToken, additionalParameters);
OidcUser oidcUser = this.userService.loadUser(userRequest);
Assert.notNull(oidcUser, "oidcUser cannot be null");
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oidcUser.getAuthorities());
OAuth2AuthenticationToken authenticationResult = new OAuth2AuthenticationToken(oidcUser, mappedAuthorities,
@ -216,7 +220,9 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -216,7 +220,9 @@ public final class OidcAuthorizedClientRefreshedEventListener
private Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {
try {
Map<String, Object> parameters = accessTokenResponse.getAdditionalParameters();
return jwtDecoder.decode((String) parameters.get(OidcParameterNames.ID_TOKEN));
String idToken = (String) parameters.get(OidcParameterNames.ID_TOKEN);
Assert.hasText(idToken, "id_token parameter cannot be null or empty");
return jwtDecoder.decode(idToken);
}
catch (JwtException ex) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
@ -250,7 +256,10 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -250,7 +256,10 @@ public final class OidcAuthorizedClientRefreshedEventListener
}
private void validateIssuer(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getIssuer().toString().equals(existingOidcUser.getIdToken().getIssuer().toString())) {
URL idTokenIssuer = idToken.getIssuer();
URL existingIdTokenIssuer = existingOidcUser.getIdToken().getIssuer();
if (idTokenIssuer == null || existingIdTokenIssuer == null
|| !idTokenIssuer.toString().equals(existingIdTokenIssuer.toString())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issuer",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -258,7 +267,10 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -258,7 +267,10 @@ public final class OidcAuthorizedClientRefreshedEventListener
}
private void validateSubject(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getSubject().equals(existingOidcUser.getIdToken().getSubject())) {
String idTokenSubject = idToken.getSubject();
String existingIdTokenSubject = existingOidcUser.getIdToken().getSubject();
if (idTokenSubject == null || existingIdTokenSubject == null
|| !idTokenSubject.equals(existingIdTokenSubject)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid subject",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -266,7 +278,10 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -266,7 +278,10 @@ public final class OidcAuthorizedClientRefreshedEventListener
}
private void validateIssuedAt(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!idToken.getIssuedAt().isAfter(existingOidcUser.getIdToken().getIssuedAt().minus(this.clockSkew))) {
Instant idTokenIssuedAt = idToken.getIssuedAt();
Instant existingIdTokenIssuedAt = existingOidcUser.getIdToken().getIssuedAt();
if (idTokenIssuedAt == null || existingIdTokenIssuedAt == null
|| !idTokenIssuedAt.isAfter(existingIdTokenIssuedAt.minus(this.clockSkew))) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid issued at time",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -283,12 +298,13 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -283,12 +298,13 @@ public final class OidcAuthorizedClientRefreshedEventListener
private boolean isValidAudience(OidcUser existingOidcUser, OidcIdToken idToken) {
List<String> idTokenAudiences = idToken.getAudience();
Set<String> oidcUserAudiences = new HashSet<>(existingOidcUser.getIdToken().getAudience());
if (idTokenAudiences.size() != oidcUserAudiences.size()) {
List<String> existingIdTokenAudiences = existingOidcUser.getIdToken().getAudience();
if (idTokenAudiences == null || existingIdTokenAudiences == null
|| idTokenAudiences.size() != existingIdTokenAudiences.size()) {
return false;
}
for (String audience : idTokenAudiences) {
if (!oidcUserAudiences.contains(audience)) {
if (!existingIdTokenAudiences.contains(audience)) {
return false;
}
}
@ -300,7 +316,7 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -300,7 +316,7 @@ public final class OidcAuthorizedClientRefreshedEventListener
return;
}
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())) {
if (!Objects.equals(idToken.getAuthenticatedAt(), existingOidcUser.getIdToken().getAuthenticatedAt())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid authenticated at time",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
@ -312,7 +328,7 @@ public final class OidcAuthorizedClientRefreshedEventListener @@ -312,7 +328,7 @@ public final class OidcAuthorizedClientRefreshedEventListener
return;
}
if (!idToken.getNonce().equals(existingOidcUser.getIdToken().getNonce())) {
if (!Objects.equals(idToken.getNonce(), existingOidcUser.getIdToken().getNonce())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE, "Invalid nonce",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());

22
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java

@ -76,8 +76,9 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> { @@ -76,8 +76,9 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
// during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
String metadataIssuer = this.clientRegistration.getProviderDetails().getIssuerUri();
if (metadataIssuer != null && !Objects.equals(metadataIssuer, idToken.getIssuer().toExternalForm())) {
invalidClaims.put(IdTokenClaimNames.ISS, idToken.getIssuer());
URL issuer = idToken.getIssuer();
if (metadataIssuer != null && issuer != null && !Objects.equals(metadataIssuer, issuer.toExternalForm())) {
invalidClaims.put(IdTokenClaimNames.ISS, issuer);
}
// 3. The Client MUST validate that the aud (audience) Claim contains its
// client_id value
@ -86,13 +87,14 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> { @@ -86,13 +87,14 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
// The ID Token MUST be rejected if the ID Token does not list the Client as a
// valid audience,
// or if it contains additional audiences not trusted by the Client.
if (!idToken.getAudience().contains(this.clientRegistration.getClientId())) {
invalidClaims.put(IdTokenClaimNames.AUD, idToken.getAudience());
List<String> audience = idToken.getAudience();
if (audience == null || !audience.contains(this.clientRegistration.getClientId())) {
invalidClaims.put(IdTokenClaimNames.AUD, audience);
}
// 4. If the ID Token contains multiple audiences,
// the Client SHOULD verify that an azp Claim is present.
String authorizedParty = idToken.getClaimAsString(IdTokenClaimNames.AZP);
if (idToken.getAudience().size() > 1 && authorizedParty == null) {
if (audience != null && audience.size() > 1 && authorizedParty == null) {
invalidClaims.put(IdTokenClaimNames.AZP, authorizedParty);
}
// 5. If an azp (authorized party) Claim is present,
@ -106,15 +108,17 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> { @@ -106,15 +108,17 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
// TODO Depends on gh-4413
// 9. The current time MUST be before the time represented by the exp Claim.
Instant now = Instant.now(this.clock);
if (now.minus(this.clockSkew).isAfter(idToken.getExpiresAt())) {
invalidClaims.put(IdTokenClaimNames.EXP, idToken.getExpiresAt());
Instant expiresAt = idToken.getExpiresAt();
if (expiresAt != null && now.minus(this.clockSkew).isAfter(expiresAt)) {
invalidClaims.put(IdTokenClaimNames.EXP, expiresAt);
}
// 10. The iat Claim can be used to reject tokens that were issued too far away
// from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.
// The acceptable range is Client specific.
if (now.plus(this.clockSkew).isBefore(idToken.getIssuedAt())) {
invalidClaims.put(IdTokenClaimNames.IAT, idToken.getIssuedAt());
Instant issuedAt = idToken.getIssuedAt();
if (issuedAt != null && now.plus(this.clockSkew).isBefore(issuedAt)) {
invalidClaims.put(IdTokenClaimNames.IAT, issuedAt);
}
if (!invalidClaims.isEmpty()) {
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/event/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Events for OpenID Connect 1.0 authentication.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.authentication.event;
import org.jspecify.annotations.NullMarked;

25
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/LogoutTokenClaimAccessor.java

@ -21,7 +21,10 @@ import java.time.Instant; @@ -21,7 +21,10 @@ import java.time.Instant;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.util.Assert;
/**
* A {@link ClaimAccessor} for the &quot;claims&quot; that can be returned in OIDC Logout
@ -41,14 +44,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor { @@ -41,14 +44,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor {
* @return the Issuer identifier
*/
default URL getIssuer() {
return this.getClaimAsURL(LogoutTokenClaimNames.ISS);
URL issuer = this.getClaimAsURL(LogoutTokenClaimNames.ISS);
Assert.notNull(issuer, "issuer cannot be null");
return issuer;
}
/**
* Returns the Subject identifier {@code (sub)}.
* @return the Subject identifier
*/
default String getSubject() {
default @Nullable String getSubject() {
return this.getClaimAsString(LogoutTokenClaimNames.SUB);
}
@ -57,14 +62,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor { @@ -57,14 +62,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor {
* @return the Audience(s) that this ID Token is intended for
*/
default List<String> getAudience() {
return this.getClaimAsStringList(LogoutTokenClaimNames.AUD);
List<String> audience = this.getClaimAsStringList(LogoutTokenClaimNames.AUD);
Assert.notNull(audience, "audience cannot be null");
return audience;
}
/**
* Returns the time at which the ID Token was issued {@code (iat)}.
* @return the time at which the ID Token was issued
*/
default Instant getIssuedAt() {
default @Nullable Instant getIssuedAt() {
return this.getClaimAsInstant(LogoutTokenClaimNames.IAT);
}
@ -73,14 +80,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor { @@ -73,14 +80,16 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor {
* @return the identifying {@link Map}
*/
default Map<String, Object> getEvents() {
return getClaimAsMap(LogoutTokenClaimNames.EVENTS);
Map<String, Object> events = getClaimAsMap(LogoutTokenClaimNames.EVENTS);
Assert.notNull(events, "events cannot be null");
return events;
}
/**
* Returns a {@code String} value {@code (sid)} representing the OIDC Provider session
* @return the value representing the OIDC Provider session
*/
default String getSessionId() {
default @Nullable String getSessionId() {
return getClaimAsString(LogoutTokenClaimNames.SID);
}
@ -90,7 +99,9 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor { @@ -90,7 +99,9 @@ public interface LogoutTokenClaimAccessor extends ClaimAccessor {
* @return the JWT ID claim which provides a unique identifier for the JWT
*/
default String getId() {
return this.getClaimAsString(LogoutTokenClaimNames.JTI);
String jti = this.getClaimAsString(LogoutTokenClaimNames.JTI);
Assert.hasText(jti, "jti cannot be empty");
return jti;
}
}

11
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/OidcLogoutToken.java

@ -24,6 +24,8 @@ import java.util.LinkedHashMap; @@ -24,6 +24,8 @@ import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.util.Assert;
@ -59,10 +61,10 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC @@ -59,10 +61,10 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC
* @param issuedAt the time at which the Logout Token was issued {@code (iat)}
* @param claims the claims about the logout statement
*/
OidcLogoutToken(String tokenValue, Instant issuedAt, Map<String, Object> claims) {
OidcLogoutToken(String tokenValue, @Nullable Instant issuedAt, Map<String, Object> claims) {
super(tokenValue, issuedAt, Instant.MAX);
this.claims = Collections.unmodifiableMap(claims);
Assert.notNull(claims, "claims must not be null");
this.claims = Collections.unmodifiableMap(claims);
}
@Override
@ -201,7 +203,8 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC @@ -201,7 +203,8 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC
"logout token must contain an events claim that contains a member called " + "'"
+ BACKCHANNEL_LOGOUT_TOKEN_EVENT_NAME + "' whose value is an empty Map");
Assert.isNull(this.claims.get("nonce"), "logout token must not contain a nonce claim");
Instant iat = toInstant(this.claims.get(IdTokenClaimNames.IAT));
Object iatClaim = this.claims.get(IdTokenClaimNames.IAT);
Instant iat = (iatClaim != null) ? toInstant(iatClaim) : null;
return new OidcLogoutToken(this.tokenValue, iat, this.claims);
}
@ -215,7 +218,7 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC @@ -215,7 +218,7 @@ public class OidcLogoutToken extends AbstractOAuth2Token implements LogoutTokenC
return object.isEmpty();
}
private Instant toInstant(Object timestamp) {
private @Nullable Instant toInstant(@Nullable Object timestamp) {
if (timestamp != null) {
Assert.isInstanceOf(Instant.class, timestamp, "timestamps must be of type Instant");
}

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/logout/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Support for OpenID Connect 1.0 Logout.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.authentication.logout;
import org.jspecify.annotations.NullMarked;

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Support classes and interfaces for authenticating and authorizing a client with an
* OpenID Connect 1.0 Provider using a specific authorization grant flow.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.authentication;
import org.jspecify.annotations.NullMarked;

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/server/session/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Reactive support for OpenID Connect 1.0 Session Management.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.server.session;
import org.jspecify.annotations.NullMarked;

19
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/session/InMemoryOidcSessionRegistry.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client.oidc.session;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -26,6 +27,7 @@ import java.util.function.Predicate; @@ -26,6 +27,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.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames;
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
@ -96,7 +98,11 @@ public final class InMemoryOidcSessionRegistry implements OidcSessionRegistry { @@ -96,7 +98,11 @@ public final class InMemoryOidcSessionRegistry implements OidcSessionRegistry {
String sessionId) {
return (session) -> {
List<String> thatAudience = session.getPrincipal().getAudience();
String thatIssuer = session.getPrincipal().getIssuer().toString();
URL thatIssuerUrl = session.getPrincipal().getIssuer();
if (thatIssuerUrl == null) {
return false;
}
String thatIssuer = thatIssuerUrl.toString();
String thatSessionId = session.getPrincipal().getClaimAsString(LogoutTokenClaimNames.SID);
if (thatAudience == null) {
return false;
@ -107,10 +113,17 @@ public final class InMemoryOidcSessionRegistry implements OidcSessionRegistry { @@ -107,10 +113,17 @@ public final class InMemoryOidcSessionRegistry implements OidcSessionRegistry {
}
private static Predicate<OidcSessionInformation> subjectMatcher(List<String> audience, String issuer,
String subject) {
@Nullable String subject) {
return (session) -> {
if (subject == null) {
return false;
}
List<String> thatAudience = session.getPrincipal().getAudience();
String thatIssuer = session.getPrincipal().getIssuer().toString();
URL thatIssuerUrl = session.getPrincipal().getIssuer();
if (thatIssuerUrl == null) {
return false;
}
String thatIssuer = thatIssuerUrl.toString();
String thatSubject = session.getPrincipal().getSubject();
if (thatAudience == null) {
return false;

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/session/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Support for OpenID Connect 1.0 Session Management.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.session;
import org.jspecify.annotations.NullMarked;

1
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java

@ -99,6 +99,7 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU @@ -99,6 +99,7 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
OAuth2User oauth2User = null;
if (this.retrieveUserInfo.test(userRequest)) {
oauth2User = this.oauth2UserService.loadUser(userRequest);
Assert.notNull(oauth2User, "oauth2User cannot be null");
Map<String, Object> claims = getClaims(userRequest, oauth2User);
userInfo = new OidcUserInfo(claims);
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Classes and interfaces providing support to the client for initiating requests to the
* OpenID Connect 1.0 Provider's UserInfo Endpoint.
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.userinfo;
import org.jspecify.annotations.NullMarked;

34
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java

@ -23,6 +23,7 @@ import java.util.Map; @@ -23,6 +23,7 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@ -49,32 +50,35 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc @@ -49,32 +50,35 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc
private final ClientRegistrationRepository clientRegistrationRepository;
private String postLogoutRedirectUri;
private @Nullable String postLogoutRedirectUri;
public OidcClientInitiatedLogoutSuccessHandler(ClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.postLogoutRedirectUri = null;
}
@Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
@Nullable Authentication authentication) {
String targetUrl = null;
if (authentication instanceof OAuth2AuthenticationToken && authentication.getPrincipal() instanceof OidcUser) {
String registrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId();
ClientRegistration clientRegistration = this.clientRegistrationRepository
.findByRegistrationId(registrationId);
URI endSessionEndpoint = this.endSessionEndpoint(clientRegistration);
if (endSessionEndpoint != null) {
String idToken = idToken(authentication);
String postLogoutRedirectUri = postLogoutRedirectUri(request, clientRegistration);
targetUrl = endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri);
if (clientRegistration != null) {
URI endSessionEndpoint = this.endSessionEndpoint(clientRegistration);
if (endSessionEndpoint != null) {
String idToken = idToken(authentication);
String postLogoutRedirectUri = postLogoutRedirectUri(request, clientRegistration);
targetUrl = endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri);
}
}
}
return (targetUrl != null) ? targetUrl : super.determineTargetUrl(request, response);
return (targetUrl != null) ? targetUrl : super.determineTargetUrl(request, response, authentication);
}
private URI endSessionEndpoint(ClientRegistration clientRegistration) {
private @Nullable URI endSessionEndpoint(@Nullable ClientRegistration clientRegistration) {
if (clientRegistration != null) {
ProviderDetails providerDetails = clientRegistration.getProviderDetails();
Object endSessionEndpoint = providerDetails.getConfigurationMetadata().get("end_session_endpoint");
@ -86,11 +90,15 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc @@ -86,11 +90,15 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc
}
private String idToken(Authentication authentication) {
return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
Object principal = authentication.getPrincipal();
String idToken = (principal instanceof OidcUser oidcUser) ? oidcUser.getIdToken().getTokenValue() : null;
Assert.notNull(idToken, "idToken cannot be null");
return idToken;
}
private String postLogoutRedirectUri(HttpServletRequest request, ClientRegistration clientRegistration) {
if (this.postLogoutRedirectUri == null) {
private @Nullable String postLogoutRedirectUri(HttpServletRequest request,
@Nullable ClientRegistration clientRegistration) {
if (this.postLogoutRedirectUri == null || clientRegistration == null) {
return null;
}
// @formatter:off
@ -123,7 +131,7 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc @@ -123,7 +131,7 @@ public class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogoutSucc
// @formatter:on
}
private String endpointUri(URI endSessionEndpoint, String idToken, String postLogoutRedirectUri) {
private String endpointUri(URI endSessionEndpoint, String idToken, @Nullable String postLogoutRedirectUri) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
builder.queryParam("id_token_hint", idToken);
if (postLogoutRedirectUri != null) {

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Support for OpenID Connect 1.0 Logout (servlet).
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.web.logout;
import org.jspecify.annotations.NullMarked;

14
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandler.java

@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.core.convert.converter.Converter;
@ -57,7 +58,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo @@ -57,7 +58,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private String postLogoutRedirectUri;
private @Nullable String postLogoutRedirectUri;
private Converter<RedirectUriParameters, Mono<String>> redirectUriResolver = new DefaultRedirectUriResolver();
@ -94,7 +95,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo @@ -94,7 +95,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
// @formatter:on
}
private URI endSessionEndpoint(ClientRegistration clientRegistration) {
private @Nullable URI endSessionEndpoint(@Nullable ClientRegistration clientRegistration) {
if (clientRegistration != null) {
Object endSessionEndpoint = clientRegistration.getProviderDetails()
.getConfigurationMetadata()
@ -106,7 +107,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo @@ -106,7 +107,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
return null;
}
private String endpointUri(URI endSessionEndpoint, String idToken, String postLogoutRedirectUri) {
private String endpointUri(URI endSessionEndpoint, String idToken, @Nullable String postLogoutRedirectUri) {
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(endSessionEndpoint);
builder.queryParam("id_token_hint", idToken);
if (postLogoutRedirectUri != null) {
@ -116,10 +117,13 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo @@ -116,10 +117,13 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
}
private String idToken(Authentication authentication) {
return ((OidcUser) authentication.getPrincipal()).getIdToken().getTokenValue();
Object principal = authentication.getPrincipal();
String idToken = (principal instanceof OidcUser oidcUser) ? oidcUser.getIdToken().getTokenValue() : null;
Assert.notNull(idToken, "idToken cannot be null");
return idToken;
}
private String postLogoutRedirectUri(ServerHttpRequest request, ClientRegistration clientRegistration) {
private @Nullable String postLogoutRedirectUri(ServerHttpRequest request, ClientRegistration clientRegistration) {
if (this.postLogoutRedirectUri == null) {
return null;
}

23
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/server/logout/package-info.java

@ -0,0 +1,23 @@ @@ -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.
*/
/**
* Support for OpenID Connect 1.0 Logout (reactive).
*/
@NullMarked
package org.springframework.security.oauth2.client.oidc.web.server.logout;
import org.jspecify.annotations.NullMarked;

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/package-info.java

@ -17,4 +17,7 @@ @@ -17,4 +17,7 @@
/**
* Core classes and interfaces providing support for OAuth 2.0 Client.
*/
@NullMarked
package org.springframework.security.oauth2.client;
import org.jspecify.annotations.NullMarked;

137
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java

@ -32,6 +32,7 @@ import java.util.Set; @@ -32,6 +32,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.log.LogMessage;
import org.springframework.security.oauth2.core.AuthenticationMethod;
@ -54,25 +55,25 @@ public final class ClientRegistration implements Serializable { @@ -54,25 +55,25 @@ public final class ClientRegistration implements Serializable {
private static final long serialVersionUID = 620L;
private String registrationId;
private @Nullable String registrationId;
private String clientId;
private @Nullable String clientId;
private String clientSecret;
private @Nullable String clientSecret;
private ClientAuthenticationMethod clientAuthenticationMethod;
private @Nullable ClientAuthenticationMethod clientAuthenticationMethod;
private AuthorizationGrantType authorizationGrantType;
private @Nullable AuthorizationGrantType authorizationGrantType;
private String redirectUri;
private @Nullable String redirectUri;
private Set<String> scopes = Collections.emptySet();
private ProviderDetails providerDetails = new ProviderDetails();
private String clientName;
private @Nullable String clientName;
private ClientSettings clientSettings;
private @Nullable ClientSettings clientSettings;
private ClientRegistration() {
}
@ -82,6 +83,7 @@ public final class ClientRegistration implements Serializable { @@ -82,6 +83,7 @@ public final class ClientRegistration implements Serializable {
* @return the identifier for the registration
*/
public String getRegistrationId() {
Assert.notNull(this.registrationId, "registrationId cannot be null");
return this.registrationId;
}
@ -90,6 +92,7 @@ public final class ClientRegistration implements Serializable { @@ -90,6 +92,7 @@ public final class ClientRegistration implements Serializable {
* @return the client identifier
*/
public String getClientId() {
Assert.notNull(this.clientId, "clientId cannot be null");
return this.clientId;
}
@ -98,6 +101,7 @@ public final class ClientRegistration implements Serializable { @@ -98,6 +101,7 @@ public final class ClientRegistration implements Serializable {
* @return the client secret
*/
public String getClientSecret() {
Assert.notNull(this.clientSecret, "clientSecret cannot be null");
return this.clientSecret;
}
@ -107,6 +111,7 @@ public final class ClientRegistration implements Serializable { @@ -107,6 +111,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link ClientAuthenticationMethod}
*/
public ClientAuthenticationMethod getClientAuthenticationMethod() {
Assert.notNull(this.clientAuthenticationMethod, "clientAuthenticationMethod cannot be null");
return this.clientAuthenticationMethod;
}
@ -116,6 +121,7 @@ public final class ClientRegistration implements Serializable { @@ -116,6 +121,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link AuthorizationGrantType}
*/
public AuthorizationGrantType getAuthorizationGrantType() {
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
return this.authorizationGrantType;
}
@ -137,7 +143,7 @@ public final class ClientRegistration implements Serializable { @@ -137,7 +143,7 @@ public final class ClientRegistration implements Serializable {
* @return the uri (or uri template) for the redirection endpoint
* @since 5.4
*/
public String getRedirectUri() {
public @Nullable String getRedirectUri() {
return this.redirectUri;
}
@ -162,6 +168,7 @@ public final class ClientRegistration implements Serializable { @@ -162,6 +168,7 @@ public final class ClientRegistration implements Serializable {
* @return the client or registration name
*/
public String getClientName() {
Assert.notNull(this.clientName, "clientName cannot be null");
return this.clientName;
}
@ -170,6 +177,7 @@ public final class ClientRegistration implements Serializable { @@ -170,6 +177,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link ClientSettings}
*/
public ClientSettings getClientSettings() {
Assert.notNull(this.clientSettings, "clientSettings cannot be null");
return this.clientSettings;
}
@ -220,15 +228,15 @@ public final class ClientRegistration implements Serializable { @@ -220,15 +228,15 @@ public final class ClientRegistration implements Serializable {
private static final long serialVersionUID = 620L;
private String authorizationUri;
private @Nullable String authorizationUri;
private String tokenUri;
private @Nullable String tokenUri;
private UserInfoEndpoint userInfoEndpoint = new UserInfoEndpoint();
private String jwkSetUri;
private @Nullable String jwkSetUri;
private String issuerUri;
private @Nullable String issuerUri;
private Map<String, Object> configurationMetadata = Collections.emptyMap();
@ -237,9 +245,10 @@ public final class ClientRegistration implements Serializable { @@ -237,9 +245,10 @@ public final class ClientRegistration implements Serializable {
/**
* Returns the uri for the authorization endpoint.
* @return the uri for the authorization endpoint
* @return the uri for the authorization endpoint, or {@code null} if not set
* (e.g. for grant types that do not use the authorization endpoint)
*/
public String getAuthorizationUri() {
public @Nullable String getAuthorizationUri() {
return this.authorizationUri;
}
@ -248,6 +257,7 @@ public final class ClientRegistration implements Serializable { @@ -248,6 +257,7 @@ public final class ClientRegistration implements Serializable {
* @return the uri for the token endpoint
*/
public String getTokenUri() {
Assert.notNull(this.tokenUri, "tokenUri cannot be null");
return this.tokenUri;
}
@ -261,9 +271,10 @@ public final class ClientRegistration implements Serializable { @@ -261,9 +271,10 @@ public final class ClientRegistration implements Serializable {
/**
* Returns the uri for the JSON Web Key (JWK) Set endpoint.
* @return the uri for the JSON Web Key (JWK) Set endpoint
* @return the uri for the JSON Web Key (JWK) Set endpoint, or {@code null} if not
* set
*/
public String getJwkSetUri() {
public @Nullable String getJwkSetUri() {
return this.jwkSetUri;
}
@ -271,10 +282,10 @@ public final class ClientRegistration implements Serializable { @@ -271,10 +282,10 @@ public final class ClientRegistration implements Serializable {
* Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the
* OAuth 2.0 Authorization Server.
* @return the issuer identifier uri for the OpenID Connect 1.0 provider or the
* OAuth 2.0 Authorization Server
* OAuth 2.0 Authorization Server, or {@code null} if not set
* @since 5.4
*/
public String getIssuerUri() {
public @Nullable String getIssuerUri() {
return this.issuerUri;
}
@ -294,20 +305,20 @@ public final class ClientRegistration implements Serializable { @@ -294,20 +305,20 @@ public final class ClientRegistration implements Serializable {
private static final long serialVersionUID = 620L;
private String uri;
private @Nullable String uri;
private AuthenticationMethod authenticationMethod = AuthenticationMethod.HEADER;
private String userNameAttributeName;
private @Nullable String userNameAttributeName;
UserInfoEndpoint() {
}
/**
* Returns the uri for the user info endpoint.
* @return the uri for the user info endpoint
* @return the uri for the user info endpoint, or {@code null} if not set
*/
public String getUri() {
public @Nullable String getUri() {
return this.uri;
}
@ -324,9 +335,9 @@ public final class ClientRegistration implements Serializable { @@ -324,9 +335,9 @@ public final class ClientRegistration implements Serializable {
* Returns the attribute name used to access the user's name from the user
* info response.
* @return the attribute name used to access the user's name from the user
* info response
* info response, or {@code null} if not set
*/
public String getUserNameAttributeName() {
public @Nullable String getUserNameAttributeName() {
return this.userNameAttributeName;
}
@ -347,37 +358,37 @@ public final class ClientRegistration implements Serializable { @@ -347,37 +358,37 @@ public final class ClientRegistration implements Serializable {
AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS,
AuthorizationGrantType.REFRESH_TOKEN);
private String registrationId;
private @Nullable String registrationId;
private String clientId;
private @Nullable String clientId;
private String clientSecret;
private @Nullable String clientSecret;
private ClientAuthenticationMethod clientAuthenticationMethod;
private @Nullable ClientAuthenticationMethod clientAuthenticationMethod;
private AuthorizationGrantType authorizationGrantType;
private @Nullable AuthorizationGrantType authorizationGrantType;
private String redirectUri;
private @Nullable String redirectUri;
private Set<String> scopes;
private @Nullable Set<String> scopes;
private String authorizationUri;
private @Nullable String authorizationUri;
private String tokenUri;
private @Nullable String tokenUri;
private String userInfoUri;
private @Nullable String userInfoUri;
private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER;
private String userNameAttributeName;
private @Nullable String userNameAttributeName;
private String jwkSetUri;
private @Nullable String jwkSetUri;
private String issuerUri;
private @Nullable String issuerUri;
private Map<String, Object> configurationMetadata = Collections.emptyMap();
private String clientName;
private @Nullable String clientName;
private ClientSettings clientSettings = ClientSettings.builder().build();
@ -392,7 +403,7 @@ public final class ClientRegistration implements Serializable { @@ -392,7 +403,7 @@ public final class ClientRegistration implements Serializable {
this.clientAuthenticationMethod = clientRegistration.clientAuthenticationMethod;
this.authorizationGrantType = clientRegistration.authorizationGrantType;
this.redirectUri = clientRegistration.redirectUri;
this.scopes = (clientRegistration.scopes != null) ? new HashSet<>(clientRegistration.scopes) : null;
this.scopes = new HashSet<>(clientRegistration.getScopes());
this.authorizationUri = clientRegistration.providerDetails.authorizationUri;
this.tokenUri = clientRegistration.providerDetails.tokenUri;
this.userInfoUri = clientRegistration.providerDetails.userInfoEndpoint.uri;
@ -400,12 +411,11 @@ public final class ClientRegistration implements Serializable { @@ -400,12 +411,11 @@ public final class ClientRegistration implements Serializable {
this.userNameAttributeName = clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName;
this.jwkSetUri = clientRegistration.providerDetails.jwkSetUri;
this.issuerUri = clientRegistration.providerDetails.issuerUri;
Map<String, Object> configurationMetadata = clientRegistration.providerDetails.configurationMetadata;
if (configurationMetadata != Collections.EMPTY_MAP) {
this.configurationMetadata = new HashMap<>(configurationMetadata);
}
this.configurationMetadata = new HashMap<>(clientRegistration.providerDetails.configurationMetadata);
this.clientName = clientRegistration.clientName;
this.clientSettings = clientRegistration.clientSettings;
if (clientRegistration.clientSettings != null) {
this.clientSettings = clientRegistration.clientSettings;
}
}
/**
@ -433,7 +443,7 @@ public final class ClientRegistration implements Serializable { @@ -433,7 +443,7 @@ public final class ClientRegistration implements Serializable {
* @param clientSecret the client secret
* @return the {@link Builder}
*/
public Builder clientSecret(String clientSecret) {
public Builder clientSecret(@Nullable String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
@ -479,7 +489,7 @@ public final class ClientRegistration implements Serializable { @@ -479,7 +489,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link Builder}
* @since 5.4
*/
public Builder redirectUri(String redirectUri) {
public Builder redirectUri(@Nullable String redirectUri) {
this.redirectUri = redirectUri;
return this;
}
@ -489,19 +499,21 @@ public final class ClientRegistration implements Serializable { @@ -489,19 +499,21 @@ public final class ClientRegistration implements Serializable {
* @param scope the scope(s) used for the client
* @return the {@link Builder}
*/
public Builder scope(String... scope) {
// @formatter:off
public Builder scope(String @Nullable... scope) {
if (scope != null && scope.length > 0) {
this.scopes = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(scope)));
}
return this;
}
// @formatter:on
/**
* Sets the scope(s) used for the client.
* @param scope the scope(s) used for the client
* @return the {@link Builder}
*/
public Builder scope(Collection<String> scope) {
public Builder scope(@Nullable Collection<String> scope) {
if (scope != null && !scope.isEmpty()) {
this.scopes = Collections.unmodifiableSet(new LinkedHashSet<>(scope));
}
@ -513,7 +525,7 @@ public final class ClientRegistration implements Serializable { @@ -513,7 +525,7 @@ public final class ClientRegistration implements Serializable {
* @param authorizationUri the uri for the authorization endpoint
* @return the {@link Builder}
*/
public Builder authorizationUri(String authorizationUri) {
public Builder authorizationUri(@Nullable String authorizationUri) {
this.authorizationUri = authorizationUri;
return this;
}
@ -533,7 +545,7 @@ public final class ClientRegistration implements Serializable { @@ -533,7 +545,7 @@ public final class ClientRegistration implements Serializable {
* @param userInfoUri the uri for the user info endpoint
* @return the {@link Builder}
*/
public Builder userInfoUri(String userInfoUri) {
public Builder userInfoUri(@Nullable String userInfoUri) {
this.userInfoUri = userInfoUri;
return this;
}
@ -557,7 +569,7 @@ public final class ClientRegistration implements Serializable { @@ -557,7 +569,7 @@ public final class ClientRegistration implements Serializable {
* from the user info response
* @return the {@link Builder}
*/
public Builder userNameAttributeName(String userNameAttributeName) {
public Builder userNameAttributeName(@Nullable String userNameAttributeName) {
this.userNameAttributeName = userNameAttributeName;
return this;
}
@ -567,7 +579,7 @@ public final class ClientRegistration implements Serializable { @@ -567,7 +579,7 @@ public final class ClientRegistration implements Serializable {
* @param jwkSetUri the uri for the JSON Web Key (JWK) Set endpoint
* @return the {@link Builder}
*/
public Builder jwkSetUri(String jwkSetUri) {
public Builder jwkSetUri(@Nullable String jwkSetUri) {
this.jwkSetUri = jwkSetUri;
return this;
}
@ -580,7 +592,7 @@ public final class ClientRegistration implements Serializable { @@ -580,7 +592,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link Builder}
* @since 5.4
*/
public Builder issuerUri(String issuerUri) {
public Builder issuerUri(@Nullable String issuerUri) {
this.issuerUri = issuerUri;
return this;
}
@ -592,7 +604,7 @@ public final class ClientRegistration implements Serializable { @@ -592,7 +604,7 @@ public final class ClientRegistration implements Serializable {
* @return the {@link Builder}
* @since 5.1
*/
public Builder providerConfigurationMetadata(Map<String, Object> configurationMetadata) {
public Builder providerConfigurationMetadata(@Nullable Map<String, Object> configurationMetadata) {
if (configurationMetadata != null) {
this.configurationMetadata = new LinkedHashMap<>(configurationMetadata);
}
@ -604,7 +616,7 @@ public final class ClientRegistration implements Serializable { @@ -604,7 +616,7 @@ public final class ClientRegistration implements Serializable {
* @param clientName the client or registration name
* @return the {@link Builder}
*/
public Builder clientName(String clientName) {
public Builder clientName(@Nullable String clientName) {
this.clientName = clientName;
return this;
}
@ -615,7 +627,6 @@ public final class ClientRegistration implements Serializable { @@ -615,7 +627,6 @@ public final class ClientRegistration implements Serializable {
* @return the {@link Builder}
*/
public Builder clientSettings(ClientSettings clientSettings) {
Assert.notNull(clientSettings, "clientSettings cannot be null");
this.clientSettings = clientSettings;
return this;
}
@ -643,10 +654,10 @@ public final class ClientRegistration implements Serializable { @@ -643,10 +654,10 @@ public final class ClientRegistration implements Serializable {
clientRegistration.clientId = this.clientId;
clientRegistration.clientSecret = StringUtils.hasText(this.clientSecret) ? this.clientSecret : "";
clientRegistration.clientAuthenticationMethod = (this.clientAuthenticationMethod != null)
? this.clientAuthenticationMethod : deduceClientAuthenticationMethod(clientRegistration);
? this.clientAuthenticationMethod : deduceClientAuthenticationMethod();
clientRegistration.authorizationGrantType = this.authorizationGrantType;
clientRegistration.redirectUri = this.redirectUri;
clientRegistration.scopes = this.scopes;
clientRegistration.scopes = (this.scopes != null) ? this.scopes : Collections.emptySet();
clientRegistration.providerDetails = createProviderDetails(clientRegistration);
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
: this.registrationId;
@ -654,7 +665,8 @@ public final class ClientRegistration implements Serializable { @@ -654,7 +665,8 @@ public final class ClientRegistration implements Serializable {
return clientRegistration;
}
private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) {
private ClientAuthenticationMethod deduceClientAuthenticationMethod() {
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& (!StringUtils.hasText(this.clientSecret))) {
return ClientAuthenticationMethod.NONE;
@ -676,6 +688,7 @@ public final class ClientRegistration implements Serializable { @@ -676,6 +688,7 @@ public final class ClientRegistration implements Serializable {
}
private void validateAuthorizationCodeGrantType() {
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
Assert.isTrue(AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType),
() -> "authorizationGrantType must be " + AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
Assert.hasText(this.registrationId, "registrationId cannot be empty");
@ -686,6 +699,7 @@ public final class ClientRegistration implements Serializable { @@ -686,6 +699,7 @@ public final class ClientRegistration implements Serializable {
}
private void validateClientCredentialsGrantType() {
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
Assert.isTrue(AuthorizationGrantType.CLIENT_CREDENTIALS.equals(this.authorizationGrantType),
() -> "authorizationGrantType must be " + AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
Assert.hasText(this.registrationId, "registrationId cannot be empty");
@ -694,6 +708,7 @@ public final class ClientRegistration implements Serializable { @@ -694,6 +708,7 @@ public final class ClientRegistration implements Serializable {
}
private void validateAuthorizationGrantTypes() {
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
for (AuthorizationGrantType authorizationGrantType : AUTHORIZATION_GRANT_TYPES) {
if (authorizationGrantType.getValue().equalsIgnoreCase(this.authorizationGrantType.getValue())
&& !authorizationGrantType.equals(this.authorizationGrantType)) {
@ -718,7 +733,7 @@ public final class ClientRegistration implements Serializable { @@ -718,7 +733,7 @@ public final class ClientRegistration implements Serializable {
}
private static boolean validateScope(String scope) {
return scope == null || scope.chars()
return scope.chars()
.allMatch((c) -> withinTheRangeOf(c, 0x21, 0x21) || withinTheRangeOf(c, 0x23, 0x5B)
|| withinTheRangeOf(c, 0x5D, 0x7E));
}

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrationRepository.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.registration;
import org.jspecify.annotations.Nullable;
/**
* A repository for OAuth 2.0 / OpenID Connect 1.0 {@link ClientRegistration}(s).
*
@ -37,6 +39,6 @@ public interface ClientRegistrationRepository { @@ -37,6 +39,6 @@ public interface ClientRegistrationRepository {
* @param registrationId the registration identifier
* @return the {@link ClientRegistration} if found, otherwise {@code null}
*/
ClientRegistration findByRegistrationId(String registrationId);
@Nullable ClientRegistration findByRegistrationId(String registrationId);
}

20
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java

@ -27,6 +27,7 @@ import com.nimbusds.oauth2.sdk.ParseException; @@ -27,6 +27,7 @@ import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import net.minidev.json.JSONObject;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
@ -199,6 +200,7 @@ public final class ClientRegistrations { @@ -199,6 +200,7 @@ public final class ClientRegistrations {
return () -> {
RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build();
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
Assert.notNull(configuration, "OIDC provider configuration cannot be null");
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer)
.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
@ -249,6 +251,7 @@ public final class ClientRegistrations { @@ -249,6 +251,7 @@ public final class ClientRegistrations {
return () -> {
RequestEntity<Void> request = RequestEntity.get(uri.toUriString()).build();
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
Assert.notNull(configuration, "Authorization server configuration cannot be null");
AuthorizationServerMetadata metadata = parse(configuration, AuthorizationServerMetadata::parse);
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer);
URI jwkSetUri = metadata.getJWKSetURI();
@ -318,22 +321,29 @@ public final class ClientRegistrations { @@ -318,22 +321,29 @@ public final class ClientRegistrations {
+ "not match the requested issuer \"" + issuer + "\"");
String name = URI.create(issuer).getHost();
ClientAuthenticationMethod method = getClientAuthenticationMethod(metadata.getTokenEndpointAuthMethods());
URI authorizationEndpointURI = metadata.getAuthorizationEndpointURI();
URI tokenEndpointURI = metadata.getTokenEndpointURI();
ClientAuthenticationMethod authMethod = (method != null) ? method
: ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
Map<String, Object> configurationMetadata = new LinkedHashMap<>(metadata.toJSONObject());
// @formatter:off
return ClientRegistration.withRegistrationId(name)
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(name)
.userNameAttributeName(IdTokenClaimNames.SUB)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientAuthenticationMethod(method)
.clientAuthenticationMethod(authMethod)
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
.authorizationUri((metadata.getAuthorizationEndpointURI() != null) ? metadata.getAuthorizationEndpointURI().toASCIIString() : null)
.authorizationUri((authorizationEndpointURI != null) ? authorizationEndpointURI.toASCIIString() : null)
.providerConfigurationMetadata(configurationMetadata)
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
.issuerUri(issuer)
.clientName(issuer);
if (tokenEndpointURI != null) {
builder.tokenUri(tokenEndpointURI.toASCIIString());
}
return builder;
// @formatter:on
}
private static ClientAuthenticationMethod getClientAuthenticationMethod(
private static @Nullable ClientAuthenticationMethod getClientAuthenticationMethod(
List<com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod> metadataAuthMethods) {
if (metadataAuthMethods == null || metadataAuthMethods
.contains(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/InMemoryClientRegistrationRepository.java

@ -23,6 +23,8 @@ import java.util.List; @@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@ -87,7 +89,7 @@ public final class InMemoryClientRegistrationRepository @@ -87,7 +89,7 @@ public final class InMemoryClientRegistrationRepository
}
@Override
public ClientRegistration findByRegistrationId(String registrationId) {
public @Nullable ClientRegistration findByRegistrationId(String registrationId) {
Assert.hasText(registrationId, "registrationId cannot be empty");
return this.registrations.get(registrationId);
}

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/SupplierClientRegistrationRepository.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client.registration; @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client.registration;
import java.util.Iterator;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
@ -48,7 +50,7 @@ public final class SupplierClientRegistrationRepository @@ -48,7 +50,7 @@ public final class SupplierClientRegistrationRepository
}
@Override
public ClientRegistration findByRegistrationId(String registrationId) {
public @Nullable ClientRegistration findByRegistrationId(String registrationId) {
Assert.hasText(registrationId, "registrationId cannot be empty");
return this.repositorySupplier.get().findByRegistrationId(registrationId);
}

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Classes and interfaces that provide support for
* {@link org.springframework.security.oauth2.client.registration.ClientRegistration}.
*/
@NullMarked
package org.springframework.security.oauth2.client.registration;
import org.jspecify.annotations.NullMarked;

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java

@ -94,7 +94,9 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq @@ -94,7 +94,9 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
OAuth2AccessToken token = userRequest.getAccessToken();
Map<String, Object> attributes = this.attributesConverter.convert(userRequest).convert(response.getBody());
Map<String, Object> body = response.getBody();
Assert.notNull(body, "userInfo response body cannot be null");
Map<String, Object> attributes = this.attributesConverter.convert(userRequest).convert(body);
Collection<GrantedAuthority> authorities = getAuthorities(token, attributes, userNameAttributeName);
return new DefaultOAuth2User(authorities, attributes, userNameAttributeName);
}

11
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java

@ -140,11 +140,12 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi @@ -140,11 +140,12 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
})
.onErrorMap((ex) -> (ex instanceof UnsupportedMediaTypeException ||
ex.getCause() instanceof UnsupportedMediaTypeException), (ex) -> {
String contentType = (ex instanceof UnsupportedMediaTypeException) ?
((UnsupportedMediaTypeException) ex).getContentType().toString() :
((UnsupportedMediaTypeException) ex.getCause()).getContentType().toString();
.onErrorMap((ex) -> (ex instanceof UnsupportedMediaTypeException
|| (ex.getCause() != null && ex.getCause() instanceof UnsupportedMediaTypeException)), (ex) -> {
UnsupportedMediaTypeException umte = (ex instanceof UnsupportedMediaTypeException)
? (UnsupportedMediaTypeException) ex : (UnsupportedMediaTypeException) ex.getCause();
String contentType = (umte != null && umte.getContentType() != null)
? umte.getContentType().toString() : "unknown";
String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '"
+ userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUri()

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DelegatingOAuth2UserService.java

@ -21,6 +21,8 @@ import java.util.Collections; @@ -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.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
@ -56,7 +58,7 @@ public class DelegatingOAuth2UserService<R extends OAuth2UserRequest, U extends @@ -56,7 +58,7 @@ public class DelegatingOAuth2UserService<R extends OAuth2UserRequest, U extends
}
@Override
public U loadUser(R userRequest) throws OAuth2AuthenticationException {
public @Nullable U loadUser(R userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
// @formatter:off
return this.userServices.stream()

8
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserRequestEntityConverter.java

@ -27,6 +27,7 @@ import org.springframework.http.RequestEntity; @@ -27,6 +27,7 @@ import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
@ -54,13 +55,12 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq @@ -54,13 +55,12 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
String userInfoUri = clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri();
Assert.hasText(userInfoUri, "UserInfo Endpoint Uri is required");
HttpMethod httpMethod = getHttpMethod(clientRegistration);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
URI uri = UriComponentsBuilder
.fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.build()
.toUri();
URI uri = UriComponentsBuilder.fromUriString(userInfoUri).build().toUri();
RequestEntity<?> request;
if (HttpMethod.POST.equals(httpMethod)) {

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserService.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.userinfo;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
@ -46,6 +48,6 @@ public interface OAuth2UserService<R extends OAuth2UserRequest, U extends OAuth2 @@ -46,6 +48,6 @@ public interface OAuth2UserService<R extends OAuth2UserRequest, U extends OAuth2
* @throws OAuth2AuthenticationException if an error occurs while attempting to obtain
* the user attributes from the UserInfo Endpoint
*/
U loadUser(R userRequest) throws OAuth2AuthenticationException;
@Nullable U loadUser(R userRequest) throws OAuth2AuthenticationException;
}

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/package-info.java

@ -18,4 +18,7 @@ @@ -18,4 +18,7 @@
* Classes and interfaces providing support to the client for initiating requests to the
* OAuth 2.0 Authorization Server's UserInfo Endpoint.
*/
@NullMarked
package org.springframework.security.oauth2.client.userinfo;
import org.jspecify.annotations.NullMarked;

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java

@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web; @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
@ -74,7 +75,7 @@ public final class AuthenticatedPrincipalOAuth2AuthorizedClientRepository implem @@ -74,7 +75,7 @@ public final class AuthenticatedPrincipalOAuth2AuthorizedClientRepository implem
}
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
public <T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
Authentication principal, HttpServletRequest request) {
if (this.isPrincipalAuthenticated(principal)) {
return this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName());

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthorizationRequestRepository.java

@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web; @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@ -45,7 +46,7 @@ public interface AuthorizationRequestRepository<T extends OAuth2AuthorizationReq @@ -45,7 +46,7 @@ public interface AuthorizationRequestRepository<T extends OAuth2AuthorizationReq
* @param request the {@code HttpServletRequest}
* @return the {@link OAuth2AuthorizationRequest} or {@code null} if not available
*/
T loadAuthorizationRequest(HttpServletRequest request);
@Nullable T loadAuthorizationRequest(HttpServletRequest request);
/**
* Persists the {@link OAuth2AuthorizationRequest} associating it to the provided
@ -65,6 +66,6 @@ public interface AuthorizationRequestRepository<T extends OAuth2AuthorizationReq @@ -65,6 +66,6 @@ public interface AuthorizationRequestRepository<T extends OAuth2AuthorizationReq
* @return the {@link OAuth2AuthorizationRequest} or {@code null} if not available
* @since 5.1
*/
T removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response);
@Nullable T removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response);
}

4
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ClientAttributes.java

@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client.web; @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.client.web;
import java.util.Map;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.util.Assert;
@ -47,7 +49,7 @@ public final class ClientAttributes { @@ -47,7 +49,7 @@ public final class ClientAttributes {
* @param attributes the attributes to search.
* @return the registration id to use.
*/
public static String resolveClientRegistrationId(Map<String, Object> attributes) {
public static @Nullable String resolveClientRegistrationId(Map<String, Object> attributes) {
return (String) attributes.get(CLIENT_REGISTRATION_ID_ATTR_NAME);
}

16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java

@ -22,9 +22,11 @@ import java.security.NoSuchAlgorithmException; @@ -22,9 +22,11 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
@ -118,7 +120,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -118,7 +120,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
public @Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = resolveRegistrationId(request);
if (registrationId == null) {
return null;
@ -128,7 +130,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -128,7 +130,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
public @Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
if (registrationId == null) {
return null;
}
@ -158,7 +160,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -158,7 +160,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
return action;
}
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId,
private @Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId,
String redirectUriAction) {
if (registrationId == null) {
return null;
@ -171,9 +173,11 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -171,9 +173,11 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
String authorizationUri = clientRegistration.getProviderDetails().getAuthorizationUri();
Assert.hasText(authorizationUri, "Authorization URI is required");
// @formatter:off
builder.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.authorizationUri(authorizationUri)
.redirectUri(redirectUriStr)
.scopes(clientRegistration.getScopes())
.state(DEFAULT_STATE_GENERATOR.generateKey());
@ -210,7 +214,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -210,7 +214,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
private String resolveRegistrationId(HttpServletRequest request) {
private @Nullable String resolveRegistrationId(HttpServletRequest request) {
if (this.authorizationRequestMatcher.matches(request)) {
return this.authorizationRequestMatcher.matcher(request)
.getVariables()
@ -263,7 +267,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au @@ -263,7 +267,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", (action != null) ? action : "");
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri())
return UriComponentsBuilder.fromUriString(Objects.requireNonNull(clientRegistration.getRedirectUri()))
.buildAndExpand(uriVariables)
.toUriString();
}

43
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java

@ -23,8 +23,8 @@ import java.util.function.Function; @@ -23,8 +23,8 @@ import java.util.function.Function;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
@ -121,15 +121,24 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori @@ -121,15 +121,24 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
this.authorizedClientRepository = authorizedClientRepository;
this.authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
this.contextAttributesMapper = new DefaultContextAttributesMapper();
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> authorizedClientRepository
.saveAuthorizedClient(authorizedClient, principal,
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName()));
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> {
HttpServletRequest request = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName());
HttpServletResponse response = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName());
Assert.notNull(request, "HttpServletRequest is required");
Assert.notNull(response, "HttpServletResponse is required");
authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, request, response);
};
this.authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) -> authorizedClientRepository.removeAuthorizedClient(
clientRegistrationId, principal,
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName())));
(clientRegistrationId, principal, attributes) -> {
HttpServletRequest request = (HttpServletRequest) attributes
.get(HttpServletRequest.class.getName());
HttpServletResponse response = (HttpServletResponse) attributes
.get(HttpServletResponse.class.getName());
Assert.notNull(request, "HttpServletRequest is required");
Assert.notNull(response, "HttpServletResponse is required");
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, request,
response);
});
}
@Nullable
@ -203,7 +212,7 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori @@ -203,7 +212,7 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
return attributes;
}
private static HttpServletRequest getHttpServletRequestOrDefault(Map<String, Object> attributes) {
private static @Nullable HttpServletRequest getHttpServletRequestOrDefault(Map<String, Object> attributes) {
HttpServletRequest servletRequest = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName());
if (servletRequest == null) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
@ -214,7 +223,7 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori @@ -214,7 +223,7 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
return servletRequest;
}
private static HttpServletResponse getHttpServletResponseOrDefault(Map<String, Object> attributes) {
private static @Nullable HttpServletResponse getHttpServletResponseOrDefault(Map<String, Object> attributes) {
HttpServletResponse servletResponse = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName());
if (servletResponse == null) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
@ -294,11 +303,13 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori @@ -294,11 +303,13 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
public Map<String, Object> apply(OAuth2AuthorizeRequest authorizeRequest) {
Map<String, Object> contextAttributes = Collections.emptyMap();
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
String scope = servletRequest.getParameter(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope)) {
contextAttributes = new HashMap<>();
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
StringUtils.delimitedListToStringArray(scope, " "));
if (servletRequest != null) {
String scope = servletRequest.getParameter(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope)) {
contextAttributes = new HashMap<>();
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
StringUtils.delimitedListToStringArray(scope, " "));
}
}
return contextAttributes;
}

21
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java

@ -132,13 +132,22 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React @@ -132,13 +132,22 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> authorizedClientRepository
.saveAuthorizedClient(authorizedClient, principal,
(ServerWebExchange) attributes.get(ServerWebExchange.class.getName()));
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) -> {
ServerWebExchange exchange = (ServerWebExchange) attributes.get(ServerWebExchange.class.getName());
if (exchange != null) {
return authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, exchange);
}
return Mono.empty();
};
this.authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) -> authorizedClientRepository.removeAuthorizedClient(
clientRegistrationId, principal,
(ServerWebExchange) attributes.get(ServerWebExchange.class.getName())));
(clientRegistrationId, principal, attributes) -> {
ServerWebExchange exchange = (ServerWebExchange) attributes.get(ServerWebExchange.class.getName());
if (exchange != null) {
return authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal,
exchange);
}
return Mono.empty();
});
}
@Override

9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java

@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.web; @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.web;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@ -44,7 +45,7 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository @@ -44,7 +45,7 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository
private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME;
@Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
public @Nullable OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
String stateParameter = getStateParameter(request);
if (stateParameter == null) {
@ -70,7 +71,7 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository @@ -70,7 +71,7 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository
}
@Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request,
public @Nullable OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request,
HttpServletResponse response) {
Assert.notNull(response, "response cannot be null");
OAuth2AuthorizationRequest authorizationRequest = loadAuthorizationRequest(request);
@ -85,11 +86,11 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository @@ -85,11 +86,11 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository
* @param request the request to use
* @return the state parameter or null if not found
*/
private String getStateParameter(HttpServletRequest request) {
private @Nullable String getStateParameter(HttpServletRequest request) {
return request.getParameter(OAuth2ParameterNames.STATE);
}
private OAuth2AuthorizationRequest getAuthorizationRequest(HttpServletRequest request) {
private @Nullable OAuth2AuthorizationRequest getAuthorizationRequest(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return (session != null) ? (OAuth2AuthorizationRequest) session.getAttribute(this.sessionAttributeName) : null;
}

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizedClientRepository.java

@ -22,6 +22,7 @@ import java.util.Map; @@ -22,6 +22,7 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@ -45,7 +46,7 @@ public final class HttpSessionOAuth2AuthorizedClientRepository implements OAuth2 @@ -45,7 +46,7 @@ public final class HttpSessionOAuth2AuthorizedClientRepository implements OAuth2
@SuppressWarnings("unchecked")
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId,
public <T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
Authentication principal, HttpServletRequest request) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.notNull(request, "request cannot be null");

19
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java

@ -28,9 +28,11 @@ import jakarta.servlet.ServletException; @@ -28,9 +28,11 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@ -193,7 +195,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { @@ -193,7 +195,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
if (authorizationRequest == null) {
return false;
}
// Compare redirect_uri
Assert.notNull(authorizationRequest.getRedirectUri(), "redirectUri cannot be null");
UriComponents requestUri = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)).build();
UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()).build();
Set<Map.Entry<String, List<String>>> requestUriParameters = new LinkedHashSet<>(
@ -220,8 +222,11 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { @@ -220,8 +222,11 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
throws IOException {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
Assert.hasText(registrationId, "registrationId cannot be empty");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
Assert.notNull(clientRegistration, "Client registration not found with id: " + registrationId);
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
String redirectUri = UrlUtils.buildFullRequestUrl(request);
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
@ -236,7 +241,9 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { @@ -236,7 +241,9 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error error = ex.getError();
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri())
String errorRedirectUri = authorizationRequest.getRedirectUri();
Assert.hasText(errorRedirectUri, "redirectUri cannot be empty");
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(errorRedirectUri)
.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
if (StringUtils.hasLength(error.getDescription())) {
uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
@ -249,17 +256,21 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { @@ -249,17 +256,21 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
}
Authentication currentAuthentication = this.securityContextHolderStrategy.getContext().getAuthentication();
String principalName = (currentAuthentication != null) ? currentAuthentication.getName() : "anonymousUser";
Authentication principal = (currentAuthentication != null) ? currentAuthentication
: new AnonymousAuthenticationToken("anonymous", principalName,
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
Assert.notNull(authenticationResult.getAccessToken(), "accessToken cannot be null");
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),
authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request,
response);
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, request, response);
String redirectUrl = authorizationRequest.getRedirectUri();
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest != null) {
redirectUrl = savedRequest.getRedirectUrl();
this.requestCache.removeRequest(request, response);
}
Assert.hasText(redirectUrl, "redirectUrl cannot be empty");
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}

18
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestRedirectFilter.java

@ -240,14 +240,16 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt @@ -240,14 +240,16 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
private void unsuccessfulRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
AuthenticationException ex) throws IOException {
Throwable cause = ex.getCause();
LogMessage message = LogMessage.format("Authorization Request failed: %s", cause);
if (InvalidClientRegistrationIdException.class.isAssignableFrom(cause.getClass())) {
// Log an invalid registrationId at WARN level to allow these errors to be
// tuned separately from other errors
this.logger.warn(message, ex);
}
else {
this.logger.error(message, ex);
if (cause != null) {
LogMessage message = LogMessage.format("Authorization Request failed: %s", cause);
if (InvalidClientRegistrationIdException.class.isAssignableFrom(cause.getClass())) {
// Log an invalid registrationId at WARN level to allow these errors to be
// tuned separately from other errors
this.logger.warn(message, ex);
}
else {
this.logger.error(message, ex);
}
}
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationRequestResolver.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.security.oauth2.client.web;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@ -41,7 +42,7 @@ public interface OAuth2AuthorizationRequestResolver { @@ -41,7 +42,7 @@ public interface OAuth2AuthorizationRequestResolver {
* @return the resolved {@link OAuth2AuthorizationRequest} or {@code null} if not
* available
*/
OAuth2AuthorizationRequest resolve(HttpServletRequest request);
@Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request);
/**
* Returns the {@link OAuth2AuthorizationRequest} resolved from the provided
@ -51,6 +52,6 @@ public interface OAuth2AuthorizationRequestResolver { @@ -51,6 +52,6 @@ public interface OAuth2AuthorizationRequestResolver {
* @return the resolved {@link OAuth2AuthorizationRequest} or {@code null} if not
* available
*/
OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId);
@Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId);
}

30
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationResponseUtils.java

@ -67,18 +67,30 @@ final class OAuth2AuthorizationResponseUtils { @@ -67,18 +67,30 @@ final class OAuth2AuthorizationResponseUtils {
String errorCode = request.getFirst(OAuth2ParameterNames.ERROR);
String state = request.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(code)) {
return OAuth2AuthorizationResponse.success(code).redirectUri(redirectUri).state(state).build();
OAuth2AuthorizationResponse.Builder builder = OAuth2AuthorizationResponse.success(code)
.redirectUri(redirectUri);
if (state != null) {
builder.state(state);
}
return builder.build();
}
if (!StringUtils.hasText(errorCode)) {
errorCode = "unknown_error";
}
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
// @formatter:off
return OAuth2AuthorizationResponse.error(errorCode)
.redirectUri(redirectUri)
.errorDescription(errorDescription)
.errorUri(errorUri)
.state(state)
.build();
// @formatter:on
OAuth2AuthorizationResponse.Builder builder = OAuth2AuthorizationResponse.error(errorCode)
.redirectUri(redirectUri);
if (errorDescription != null) {
builder.errorDescription(errorDescription);
}
if (errorUri != null) {
builder.errorUri(errorUri);
}
if (state != null) {
builder.state(state);
}
return builder.build();
}
}

5
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizedClientRepository.java

@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web; @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.web;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
@ -54,8 +55,8 @@ public interface OAuth2AuthorizedClientRepository { @@ -54,8 +55,8 @@ public interface OAuth2AuthorizedClientRepository {
* @param <T> a type of OAuth2AuthorizedClient
* @return the {@link OAuth2AuthorizedClient} or {@code null} if not available
*/
<T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, Authentication principal,
HttpServletRequest request);
<T extends OAuth2AuthorizedClient> @Nullable T loadAuthorizedClient(String clientRegistrationId,
Authentication principal, HttpServletRequest request);
/**
* Saves the {@link OAuth2AuthorizedClient} associating it to the provided End-User

3
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java

@ -178,6 +178,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce @@ -178,6 +178,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
Assert.hasText(registrationId, "registrationId cannot be empty");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
@ -203,6 +204,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce @@ -203,6 +204,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
.convert(authenticationResult);
Assert.notNull(oauth2Authentication, "authentication result cannot be null");
oauth2Authentication.setDetails(authenticationDetails);
Assert.notNull(authenticationResult.getAccessToken(), "accessToken cannot be null");
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
@ -237,6 +239,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce @@ -237,6 +239,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
}
private OAuth2AuthenticationToken createAuthenticationResult(OAuth2LoginAuthenticationToken authenticationResult) {
Assert.notNull(authenticationResult.getPrincipal(), "principal cannot be null");
return new OAuth2AuthenticationToken(authenticationResult.getPrincipal(), authenticationResult.getAuthorities(),
authenticationResult.getClientRegistration().getRegistrationId());
}

20
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/client/OAuth2ClientHttpRequestInterceptor.java

@ -22,6 +22,7 @@ import java.util.Map; @@ -22,6 +22,7 @@ import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@ -30,7 +31,6 @@ import org.springframework.http.HttpStatusCode; @@ -30,7 +31,6 @@ import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@ -186,8 +186,10 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque @@ -186,8 +186,10 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
.get(HttpServletRequest.class.getName());
HttpServletResponse response = (HttpServletResponse) attributes
.get(HttpServletResponse.class.getName());
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, request,
response);
if (request != null && response != null) {
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, request,
response);
}
});
}
@ -256,7 +258,9 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque @@ -256,7 +258,9 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
return response;
}
catch (RestClientResponseException ex) {
handleAuthorizationFailure(request, principal, ex.getResponseHeaders(), ex.getStatusCode());
HttpHeaders responseHeaders = ex.getResponseHeaders();
handleAuthorizationFailure(request, principal,
(responseHeaders != null) ? responseHeaders : new HttpHeaders(), ex.getStatusCode());
throw ex;
}
catch (OAuth2AuthorizationException ex) {
@ -297,7 +301,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque @@ -297,7 +301,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
handleAuthorizationFailure(authorizationException, principal);
}
private static OAuth2Error resolveOAuth2ErrorIfPossible(HttpHeaders headers, HttpStatusCode httpStatus) {
private static @Nullable OAuth2Error resolveOAuth2ErrorIfPossible(HttpHeaders headers, HttpStatusCode httpStatus) {
String wwwAuthenticateHeader = headers.getFirst(HttpHeaders.WWW_AUTHENTICATE);
if (wwwAuthenticateHeader != null) {
Map<String, String> parameters = parseWwwAuthenticateHeader(wwwAuthenticateHeader);
@ -366,8 +370,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque @@ -366,8 +370,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
* @return the {@code clientRegistrationId} to be used for resolving an
* {@link OAuth2AuthorizedClient}.
*/
@Nullable
String resolve(HttpRequest request);
@Nullable String resolve(HttpRequest request);
}
@ -386,8 +389,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque @@ -386,8 +389,7 @@ public final class OAuth2ClientHttpRequestInterceptor implements ClientHttpReque
* @return the {@link Authentication principal} to be used for resolving an
* {@link OAuth2AuthorizedClient}.
*/
@Nullable
Authentication resolve(HttpRequest request);
@Nullable Authentication resolve(HttpRequest request);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save