Skip to content

Commit 475fae7

Browse files
Merge branch 'spring-projects:main' into feature/oauth2resourceserverspec-success-handler
2 parents 0120900 + 1455798 commit 475fae7

File tree

81 files changed

+1267
-23
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1267
-23
lines changed

.github/workflows/check-snapshots.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
toolchain: 25
2323
with:
2424
java-version: ${{ matrix.java-version }}
25-
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
25+
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.0.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
2626
secrets: inherit
2727
send-notification:
2828
name: Send Notification

access/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
*/
3636
@Deprecated
3737
@NullUnmarked
38+
@SuppressWarnings("serial")
3839
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {
3940

4041
private final Expression authorizeExpression;

cas/src/main/java/org/springframework/security/cas/jackson/CasJacksonModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* @since 7.0
4949
* @see SecurityJacksonModules
5050
*/
51+
@SuppressWarnings("serial")
5152
public class CasJacksonModule extends SecurityJacksonModule {
5253

5354
public CasJacksonModule() {

config/src/main/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
4040
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
4141
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider;
42+
import org.springframework.security.web.webauthn.management.CredentialRecordOwnerAuthorizationManager;
4243
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
4344
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
4445
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
@@ -180,6 +181,8 @@ public void configure(H http) {
180181
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
181182
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
182183
rpOperations);
184+
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
185+
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
183186
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
184187
rpOperations);
185188
if (creationOptionsRepository != null) {

config/src/test/java/org/springframework/security/SerializationSamples.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@
8686
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
8787
import org.springframework.security.authorization.AuthorizationDecision;
8888
import org.springframework.security.authorization.AuthorizationDeniedException;
89+
import org.springframework.security.authorization.FactorAuthorizationDecision;
90+
import org.springframework.security.authorization.RequiredFactor;
91+
import org.springframework.security.authorization.RequiredFactorError;
8992
import org.springframework.security.authorization.event.AuthorizationEvent;
9093
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
9194
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
@@ -162,22 +165,30 @@
162165
import org.springframework.security.oauth2.jwt.JwtValidationException;
163166
import org.springframework.security.oauth2.jwt.TestJwts;
164167
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
168+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
165169
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
166170
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
167171
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
168172
import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection;
169173
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
170174
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
171175
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
176+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
172177
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
173178
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
174179
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
175180
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
181+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
176182
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
177183
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken;
178184
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken;
185+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationToken;
179186
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken;
180187
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken;
188+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
189+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
190+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
191+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
181192
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
182193
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
183194
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -191,6 +202,7 @@
191202
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
192203
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
193204
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
205+
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
194206
import org.springframework.security.oauth2.server.resource.BearerTokenError;
195207
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
196208
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
@@ -251,6 +263,7 @@
251263
import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
252264
import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
253265
import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
266+
import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
254267
import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
255268
import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
256269
import org.springframework.security.web.webauthn.api.Bytes;
@@ -271,6 +284,7 @@
271284
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
272285
import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
273286
import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses;
287+
import org.springframework.security.web.webauthn.api.TestAuthenticatorAttestationResponses;
274288
import org.springframework.security.web.webauthn.api.TestBytes;
275289
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
276290
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions;
@@ -445,6 +459,8 @@ final class SerializationSamples {
445459
generatorByClassName.put(RegisteredClient.class, (r) -> registeredClient);
446460
generatorByClassName.put(OAuth2Authorization.class, (r) -> authorization);
447461
generatorByClassName.put(OAuth2Authorization.Token.class, (r) -> authorization.getAccessToken());
462+
generatorByClassName.put(OAuth2AuthorizationCode.class,
463+
(r) -> new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plusSeconds(300)));
448464
generatorByClassName.put(OAuth2AuthorizationConsent.class,
449465
(r) -> OAuth2AuthorizationConsent.withId("registeredClientId", "principalName")
450466
.scope("scope1")
@@ -470,6 +486,58 @@ final class SerializationSamples {
470486
authenticationToken.setDetails(details);
471487
return authenticationToken;
472488
});
489+
generatorByClassName.put(
490+
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken.class,
491+
(r) -> {
492+
org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken token = new org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken(
493+
"code", principal, "https://localhost/callback", Map.of("custom_param", "custom_value"));
494+
token.setDetails(details);
495+
return token;
496+
});
497+
generatorByClassName.put(OAuth2AuthorizationCodeRequestAuthenticationException.class, (r) -> {
498+
OAuth2AuthorizationCodeRequestAuthenticationToken authToken = new OAuth2AuthorizationCodeRequestAuthenticationToken(
499+
"https://localhost/authorize", "clientId", principal, "https://localhost/callback", "state",
500+
authorizationRequest.getScopes(), authorizationRequest.getAdditionalParameters());
501+
return new OAuth2AuthorizationCodeRequestAuthenticationException(
502+
new OAuth2Error("invalid_request", "Missing required parameter", "https://example.com/error"),
503+
authToken);
504+
});
505+
generatorByClassName.put(OAuth2ClientCredentialsAuthenticationToken.class, (r) -> {
506+
OAuth2ClientCredentialsAuthenticationToken token = new OAuth2ClientCredentialsAuthenticationToken(principal,
507+
Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
508+
token.setDetails(details);
509+
return token;
510+
});
511+
generatorByClassName.put(OAuth2DeviceCodeAuthenticationToken.class, (r) -> {
512+
OAuth2DeviceCodeAuthenticationToken token = new OAuth2DeviceCodeAuthenticationToken("device-code",
513+
principal, Map.of("custom_param", "custom_value"));
514+
token.setDetails(details);
515+
return token;
516+
});
517+
generatorByClassName.put(OAuth2RefreshTokenAuthenticationToken.class, (r) -> {
518+
OAuth2RefreshTokenAuthenticationToken token = new OAuth2RefreshTokenAuthenticationToken("refresh-token",
519+
principal, Set.of("scope1", "scope2"), Map.of("custom_param", "custom_value"));
520+
token.setDetails(details);
521+
return token;
522+
});
523+
generatorByClassName.put(OAuth2TokenExchangeAuthenticationToken.class, (r) -> {
524+
OAuth2TokenExchangeAuthenticationToken token = new OAuth2TokenExchangeAuthenticationToken(
525+
"urn:ietf:params:oauth:token-type:access_token", "subject-token",
526+
"urn:ietf:params:oauth:token-type:jwt", principal, "actor-token",
527+
"urn:ietf:params:oauth:token-type:jwt", Set.of("https://resource.example.com"), Set.of("audience"),
528+
Set.of("scope1"), Map.of("custom_param", "custom_value"));
529+
token.setDetails(details);
530+
return token;
531+
});
532+
OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS,
533+
"https://issuer.example.com", OAuth2TokenClaimNames.SUB, "actor-subject"));
534+
generatorByClassName.put(OAuth2TokenExchangeActor.class, (r) -> actor);
535+
generatorByClassName.put(OAuth2TokenExchangeCompositeAuthenticationToken.class, (r) -> {
536+
AbstractAuthenticationToken token = new OAuth2TokenExchangeCompositeAuthenticationToken(authentication,
537+
List.of(actor));
538+
token.setDetails(details);
539+
return token;
540+
});
473541
generatorByClassName.put(OAuth2AuthorizationConsentAuthenticationToken.class, (r) -> {
474542
OAuth2AuthorizationConsentAuthenticationToken authenticationToken = new OAuth2AuthorizationConsentAuthenticationToken(
475543
"authorizationUri", "clientId", principal, "state", authorizationRequest.getScopes(),
@@ -685,6 +753,12 @@ final class SerializationSamples {
685753
generatorByClassName.put(AuthorizationDecision.class, (r) -> new AuthorizationDecision(true));
686754
generatorByClassName.put(AuthorityAuthorizationDecision.class,
687755
(r) -> new AuthorityAuthorizationDecision(true, AuthorityUtils.createAuthorityList("ROLE_USER")));
756+
RequiredFactor factor = RequiredFactor.withAuthority("authority").validDuration(Duration.ofSeconds(5)).build();
757+
generatorByClassName.put(RequiredFactor.class, (r) -> factor);
758+
RequiredFactorError error = RequiredFactorError.createMissing(factor);
759+
generatorByClassName.put(RequiredFactorError.class, (r) -> error);
760+
generatorByClassName.put(FactorAuthorizationDecision.class,
761+
(r) -> new FactorAuthorizationDecision(List.of(error)));
688762
generatorByClassName.put(CycleInRoleHierarchyException.class, (r) -> new CycleInRoleHierarchyException());
689763
generatorByClassName.put(AuthorizationEvent.class,
690764
(r) -> new AuthorizationEvent(new SerializableSupplier<>(authentication), "source",
@@ -875,6 +949,8 @@ final class SerializationSamples {
875949
generatorByClassName.put(CredentialPropertiesOutput.class, (o) -> credentialOutput);
876950
generatorByClassName.put(ImmutableAuthenticationExtensionsClientOutputs.class, (o) -> outputs);
877951
generatorByClassName.put(AuthenticatorAssertionResponse.class, (r) -> response);
952+
generatorByClassName.put(AuthenticatorAttestationResponse.class,
953+
(r) -> TestAuthenticatorAttestationResponses.createAuthenticatorAttestationResponse().build());
878954
generatorByClassName.put(RelyingPartyAuthenticationRequest.class, (r) -> authRequest);
879955
generatorByClassName.put(PublicKeyCredential.class, (r) -> credential);
880956
generatorByClassName.put(WebAuthnAuthenticationRequestToken.class, (r) -> requestToken);

config/src/test/java/org/springframework/security/SpringSecurityCoreVersionSerializableTests.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
import java.nio.file.Path;
3434
import java.nio.file.Paths;
3535
import java.util.ArrayList;
36-
import java.util.Arrays;
3736
import java.util.HashSet;
3837
import java.util.List;
3938
import java.util.Set;
39+
import java.util.regex.Pattern;
4040
import java.util.stream.Stream;
4141

4242
import org.apache.commons.lang3.ObjectUtils;
@@ -204,13 +204,13 @@ void allSerializableClassesShouldHaveSerialVersionOrSuppressWarnings() throws Ex
204204
if (clazz.getName().contains("Tests")) {
205205
continue;
206206
}
207+
if (clazz.isAnonymousClass()) {
208+
continue;
209+
}
207210
boolean hasSerialVersion = Stream.of(clazz.getDeclaredFields())
208211
.map(Field::getName)
209212
.anyMatch((n) -> n.equals("serialVersionUID"));
210-
SuppressWarnings suppressWarnings = clazz.getAnnotation(SuppressWarnings.class);
211-
boolean hasSerialIgnore = suppressWarnings == null
212-
|| Arrays.asList(suppressWarnings.value()).contains("Serial");
213-
if (!hasSerialVersion && !hasSerialIgnore) {
213+
if (!hasSerialVersion && !hasSuppressSerialInSource(clazz)) {
214214
classes.add(clazz);
215215
continue;
216216
}
@@ -249,6 +249,58 @@ static Stream<Class<?>> getClassesToSerialize() throws Exception {
249249
return classes.stream();
250250
}
251251

252+
private static boolean hasSuppressSerialInSource(Class<?> clazz) {
253+
try {
254+
Class<?> fileClass = clazz;
255+
while (fileClass.getEnclosingClass() != null) {
256+
fileClass = fileClass.getEnclosingClass();
257+
}
258+
var codeSource = fileClass.getProtectionDomain().getCodeSource();
259+
if (codeSource == null) {
260+
return false;
261+
}
262+
Path sourceFile = findSourceFile(Path.of(codeSource.getLocation().toURI()), fileClass);
263+
if (sourceFile == null) {
264+
return false;
265+
}
266+
return hasSuppressSerialAnnotation(Files.readAllLines(sourceFile), clazz.getSimpleName());
267+
}
268+
catch (Exception ex) {
269+
return false;
270+
}
271+
}
272+
273+
private static Path findSourceFile(Path start, Class<?> clazz) {
274+
String relativePath = clazz.getName().replace('.', '/') + ".java";
275+
Path dir = start;
276+
for (int i = 0; i < 10 && dir != null; i++) {
277+
for (String sourceRoot : List.of("src/main/java", "src/test/java")) {
278+
Path candidate = dir.resolve(sourceRoot).resolve(relativePath);
279+
if (Files.exists(candidate)) {
280+
return candidate;
281+
}
282+
}
283+
dir = dir.getParent();
284+
}
285+
return null;
286+
}
287+
288+
private static boolean hasSuppressSerialAnnotation(List<String> lines, String simpleClassName) {
289+
Pattern classDeclaration = Pattern
290+
.compile("\\b(?:class|interface|enum|record)\\s+" + Pattern.quote(simpleClassName) + "\\b");
291+
for (int i = 0; i < lines.size(); i++) {
292+
if (classDeclaration.matcher(lines.get(i)).find()) {
293+
for (int j = Math.max(0, i - 5); j < i; j++) {
294+
String line = lines.get(j);
295+
if (line.contains("@SuppressWarnings") && line.contains("\"serial\"")) {
296+
return true;
297+
}
298+
}
299+
}
300+
}
301+
return false;
302+
}
303+
252304
private static String getCurrentVersion() {
253305
String version = System.getProperty("springSecurityVersion");
254306
String[] parts = version.split("\\.");

0 commit comments

Comments
 (0)