mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-04-16 14:03:44 +00:00
Enable null-safety in spring-security-oauth2-client
Closes gh-17819
This commit is contained in:
parent
bb062585a8
commit
baad23caab
@ -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();
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ class ServerWebClientHttpInterfaceIntegrationConfiguration {
|
||||
fun securityConfigurer(
|
||||
manager: ReactiveOAuth2AuthorizedClientManager?
|
||||
): OAuth2WebClientHttpServiceGroupConfigurer {
|
||||
return OAuth2WebClientHttpServiceGroupConfigurer.from(manager)
|
||||
return OAuth2WebClientHttpServiceGroupConfigurer.from(requireNotNull(manager))
|
||||
}
|
||||
|
||||
// end::config[]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id 'javadoc-warnings-error'
|
||||
id 'security-nullability'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
|
||||
@ -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
|
||||
* 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) {
|
||||
|
||||
@ -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
|
||||
.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();
|
||||
|
||||
@ -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
|
||||
* 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
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@ -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
|
||||
|
||||
@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");
|
||||
|
||||
@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
* 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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
* 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;
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
*/
|
||||
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");
|
||||
|
||||
@ -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;
|
||||
*/
|
||||
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 {
|
||||
* @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 {
|
||||
* 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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
*/
|
||||
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");
|
||||
|
||||
@ -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 {
|
||||
|
||||
private final OAuth2AccessToken accessToken;
|
||||
|
||||
private final OAuth2RefreshToken refreshToken;
|
||||
private final @Nullable OAuth2RefreshToken refreshToken;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizedClient} using the provided parameters.
|
||||
|
||||
@ -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 {
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
*/
|
||||
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 {
|
||||
*/
|
||||
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() {
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
* @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
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
* @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
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public OAuth2RefreshToken getRefreshToken() {
|
||||
public @Nullable OAuth2RefreshToken getRefreshToken() {
|
||||
return this.refreshToken;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@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
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
*/
|
||||
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 {
|
||||
*/
|
||||
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() {
|
||||
}
|
||||
|
||||
@ -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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
* 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
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
* 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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
* @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
|
||||
* @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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
* Returns the {@link OAuth2AccessToken access token}.
|
||||
* @return the {@link OAuth2AccessToken}
|
||||
*/
|
||||
public OAuth2AccessToken getAccessToken() {
|
||||
public @Nullable OAuth2AccessToken getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
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));
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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
|
||||
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));
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User getPrincipal() {
|
||||
public @Nullable OAuth2User getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
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
|
||||
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);
|
||||
|
||||
@ -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;
|
||||
* 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
|
||||
}
|
||||
|
||||
@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
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static JwsAlgorithm resolveAlgorithm(JWK jwk) {
|
||||
private static @Nullable JwsAlgorithm resolveAlgorithm(JWK jwk) {
|
||||
JwsAlgorithm jwsAlgorithm = null;
|
||||
|
||||
if (jwk.getAlgorithm() != null) {
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
* @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
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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 {
|
||||
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 {
|
||||
return new OAuth2Error(errorCode, errorDescription, errorUri);
|
||||
}
|
||||
|
||||
private BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
|
||||
private @Nullable BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
|
||||
try {
|
||||
return BearerTokenError.parse(wwwAuthenticateHeader);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
? 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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
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
|
||||
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,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;
|
||||
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 {
|
||||
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 {
|
||||
@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 {
|
||||
@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 {
|
||||
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;
|
||||
|
||||
@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Jackson 3+ serialization support for OAuth2 client.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.oauth2.client.jackson;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@ -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
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
@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 {
|
||||
@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 {
|
||||
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;
|
||||
|
||||
@ -17,4 +17,7 @@
|
||||
/**
|
||||
* Jackson 2 serialization support for OAuth2 client.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.security.oauth2.client.jackson2;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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
|
||||
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
|
||||
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
|
||||
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);
|
||||
|
||||
@ -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
|
||||
.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
|
||||
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) ->
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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());
|
||||
|
||||
@ -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> {
|
||||
// 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> {
|
||||
// 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));
|
||||
|
||||
@ -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;
|
||||
@ -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 "claims" that can be returned in OIDC Logout
|
||||
@ -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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
* @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
|
||||
"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
|
||||
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");
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
// @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) {
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
|
||||
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
|
||||
// @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
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
* 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 {
|
||||
|
||||
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 {
|
||||
* 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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* 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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
* @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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
* @param registrationId the registration identifier
|
||||
* @return the {@link ClientRegistration} if found, otherwise {@code null}
|
||||
*/
|
||||
ClientRegistration findByRegistrationId(String registrationId);
|
||||
@Nullable ClientRegistration findByRegistrationId(String registrationId);
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
+ "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)) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration findByRegistrationId(String registrationId) {
|
||||
public @Nullable ClientRegistration findByRegistrationId(String registrationId) {
|
||||
Assert.hasText(registrationId, "registrationId cannot be empty");
|
||||
return this.registrations.get(registrationId);
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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()
|
||||
|
||||
@ -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
|
||||
@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)) {
|
||||
|
||||
@ -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
|
||||
* @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;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
@ -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
|
||||
* @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
|
||||
* @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);
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
@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
|
||||
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
|
||||
|
||||
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
|
||||
+ ") 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
|
||||
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();
|
||||
}
|
||||
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
* @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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@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");
|
||||
|
||||
@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
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 {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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 {
|
||||
* @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 {
|
||||
* @return the resolved {@link OAuth2AuthorizationRequest} or {@code null} if not
|
||||
* available
|
||||
*/
|
||||
OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId);
|
||||
@Nullable OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId);
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
* @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
|
||||
|
||||
@ -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
|
||||
.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
|
||||
}
|
||||
|
||||
private OAuth2AuthenticationToken createAuthenticationResult(OAuth2LoginAuthenticationToken authenticationResult) {
|
||||
Assert.notNull(authenticationResult.getPrincipal(), "principal cannot be null");
|
||||
return new OAuth2AuthenticationToken(authenticationResult.getPrincipal(), authenticationResult.getAuthorities(),
|
||||
authenticationResult.getClientRegistration().getRegistrationId());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
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
|
||||
.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
|
||||
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
|
||||
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
|
||||
* @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
|
||||
* @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…
x
Reference in New Issue
Block a user