From 9f8ac34c3b8773771ace28e2bc0e64dbce2463e5 Mon Sep 17 00:00:00 2001 From: Robert Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:10:29 -0600 Subject: [PATCH] Remove @NullUnmarked Closes gh-18491 --- .../CasAuthenticationProvider.java | 2 - .../MethodSecurityEvaluationContext.java | 8 +-- ...thenticationPrincipalArgumentResolver.java | 5 +- .../taglibs/authz/AbstractAuthorizeTag.java | 2 - ...hSecurityContextTestExecutionListener.java | 35 +++++----- .../server/SecurityMockServerConfigurers.java | 64 ++++++++++--------- .../SecurityMockMvcRequestPostProcessors.java | 26 +++----- .../SecurityMockMvcResultMatchers.java | 2 - .../setup/SecurityMockMvcConfigurer.java | 7 +- .../test/web/support/WebTestUtils.java | 12 ++-- .../ObservationWebFilterChainDecorator.java | 10 +-- .../Webauthn4JRelyingPartyOperations.java | 2 - 12 files changed, 85 insertions(+), 90 deletions(-) diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java index 1b02681f54..577c50750d 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java @@ -24,7 +24,6 @@ import org.apache.commons.logging.LogFactory; import org.apereo.cas.client.validation.Assertion; import org.apereo.cas.client.validation.TicketValidationException; import org.apereo.cas.client.validation.TicketValidator; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; @@ -166,7 +165,6 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia * @param authentication * @return */ - @NullUnmarked private @Nullable String getServiceUrl(Authentication authentication) { String serviceUrl; if (authentication.getDetails() instanceof ServiceAuthenticationDetails) { diff --git a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java index 4c72eabf83..af1d14cf1d 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContext.java @@ -19,7 +19,7 @@ package org.springframework.security.access.expression.method; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; @@ -45,13 +45,11 @@ class MethodSecurityEvaluationContext extends MethodBasedEvaluationContext { * for each instance. Use the constructor which takes the resolver, as an argument * thus allowing for caching. */ - MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi) { + MethodSecurityEvaluationContext(@Nullable Authentication user, MethodInvocation mi) { this(user, mi, new DefaultSecurityParameterNameDiscoverer()); } - @NullUnmarked // FIXME: rootObject in MethodBasedEvaluationContext is non-null - // (probably needs changed) but StandardEvaluationContext is Nullable - MethodSecurityEvaluationContext(Authentication user, MethodInvocation mi, + MethodSecurityEvaluationContext(@Nullable Authentication user, MethodInvocation mi, ParameterNameDiscoverer parameterNameDiscoverer) { super(mi.getThis(), getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java index 9017b2d5bf..b0a03bf2e0 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java +++ b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java @@ -18,7 +18,6 @@ package org.springframework.security.messaging.handler.invocation.reactive; import java.lang.annotation.Annotation; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -152,9 +151,11 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg // @formatter:on } - @NullUnmarked private @Nullable Object resolvePrincipal(MethodParameter parameter, @Nullable Object principal) { AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter); + if (authPrincipal == null) { + return null; + } String expressionToParse = authPrincipal.expression(); if (StringUtils.hasLength(expressionToParse)) { StandardEvaluationContext context = new StandardEvaluationContext(); diff --git a/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java b/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java index b41038828f..3fd0690a82 100644 --- a/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java +++ b/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java @@ -24,7 +24,6 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; @@ -180,7 +179,6 @@ public abstract class AbstractAuthorizeTag { return this.method; } - @NullUnmarked public void setMethod(String method) { this.method = (method != null) ? method.toUpperCase(Locale.ENGLISH) : null; } diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java index 9b013794e6..0dcec157cb 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java @@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.function.Supplier; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanUtils; @@ -28,6 +27,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -40,6 +41,7 @@ import org.springframework.test.context.TestContextAnnotationUtils; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.Assert; /** * A {@link TestExecutionListener} that will find annotations that are annotated with @@ -79,7 +81,6 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut * {@link WithSecurityContext} on it. If that is not found, the class is inspected. If * still not found, then no {@link SecurityContext} is populated. */ - @NullUnmarked @Override public void beforeTestMethod(TestContext testContext) { TestSecurityContext testSecurityContext = createTestSecurityContext(testContext.getTestMethod(), testContext); @@ -102,7 +103,6 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut * If configured before test execution sets the SecurityContext * @since 5.1 */ - @NullUnmarked @Override public void beforeTestExecution(TestContext testContext) { Supplier supplier = (Supplier) testContext @@ -129,7 +129,6 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut return createTestSecurityContext(rootDeclaringClass, withSecurityContext, context); } - @NullUnmarked @SuppressWarnings({ "rawtypes", "unchecked" }) private @Nullable TestSecurityContext createTestSecurityContext(AnnotatedElement annotated, @Nullable WithSecurityContext withSecurityContext, TestContext context) { @@ -140,7 +139,9 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut WithSecurityContextFactory factory = createFactory(withSecurityContext, context); Class type = (Class) GenericTypeResolver .resolveTypeArgument(factory.getClass(), WithSecurityContextFactory.class); + Assert.isTrue(type != null, factory.getClass() + " must specify a Type argument"); Annotation annotation = findAnnotation(annotated, type); + Assert.isTrue(annotation != null, "No annotation found for " + type + " on " + annotated); Supplier supplier = () -> { try { return factory.createSecurityContext(annotation); @@ -153,22 +154,23 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut return new TestSecurityContext(supplier, initialize); } - @NullUnmarked - private @Nullable Annotation findAnnotation(AnnotatedElement annotated, - @Nullable Class type) { + private @Nullable Annotation findAnnotation(AnnotatedElement annotated, Class type) { Annotation findAnnotation = AnnotatedElementUtils.findMergedAnnotation(annotated, type); if (findAnnotation != null) { return findAnnotation; } - Annotation[] allAnnotations = AnnotationUtils.getAnnotations(annotated); - for (Annotation annotationToTest : allAnnotations) { - WithSecurityContext withSecurityContext = AnnotationUtils.findAnnotation(annotationToTest.annotationType(), - WithSecurityContext.class); - if (withSecurityContext != null) { - return annotationToTest; - } - } - return null; + MergedAnnotations allAnnotations = MergedAnnotations.from(annotated); + // @formatter:off + return allAnnotations.stream() + .filter((annotationToTest) -> { + WithSecurityContext withSecurityContext = AnnotationUtils.findAnnotation(annotationToTest.getType(), + WithSecurityContext.class); + return withSecurityContext != null; + }) + .map(MergedAnnotation::synthesize) + .findFirst() + .orElse(null); + // @formatter:on } private WithSecurityContextFactory createFactory(WithSecurityContext withSecurityContext, @@ -189,7 +191,6 @@ public class WithSecurityContextTestExecutionListener extends AbstractTestExecut * Clears out the {@link TestSecurityContextHolder} and the * {@link SecurityContextHolder} after each test method. */ - @NullUnmarked @Override public void afterTestMethod(TestContext testContext) { this.securityContextHolderStrategyConverter.convert(testContext).clearContext(); diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index da424588a9..f45361382c 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -29,13 +29,12 @@ import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; -import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.converter.Converter; import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.lang.Nullable; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -191,7 +190,6 @@ public final class SecurityMockServerConfigurers { * @return the {@link OAuth2LoginMutator} to further configure or use * @since 5.3 */ - @NullUnmarked public static OAuth2LoginMutator mockOAuth2Login() { OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", null, null, Collections.singleton("read")); @@ -205,7 +203,6 @@ public final class SecurityMockServerConfigurers { * @return the {@link OidcLoginMutator} to further configure or use * @since 5.3 */ - @NullUnmarked public static OidcLoginMutator mockOidcLogin() { OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", null, null, Collections.singleton("read")); @@ -255,12 +252,15 @@ public final class SecurityMockServerConfigurers { private CsrfMutator() { } - @NullUnmarked @Override public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) { CsrfWebFilter filter = new CsrfWebFilter(); filter.setRequireCsrfProtectionMatcher((e) -> ServerWebExchangeMatcher.MatchResult.notMatch()); + if (httpHandlerBuilder == null) { + throw new UnsupportedOperationException( + "Cannot apply Csrf because WebHttpHandlerBuilder is null. You must use mock clients."); + } httpHandlerBuilder.filters((filters) -> filters.add(0, filter)); } @@ -398,11 +398,14 @@ public final class SecurityMockServerConfigurers { builder.filters(addSetupMutatorFilter()); } - @NullUnmarked @Override public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder webHttpHandlerBuilder, @Nullable ClientHttpConnector clientHttpConnector) { + if (webHttpHandlerBuilder == null) { + throw new UnsupportedOperationException( + "Cannot apply Spring Security Test Support to null WebHttpHandlerBuilder. This happens when trying to integrate with non-mock based clients."); + } webHttpHandlerBuilder.filters(addSetupMutatorFilter()); } @@ -542,10 +545,13 @@ public final class SecurityMockServerConfigurers { configurer().afterConfigureAdded(serverSpec); } - @NullUnmarked @Override public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) { + if (httpHandlerBuilder == null) { + throw new UnsupportedOperationException( + "Cannot apply Spring Security Test Support to null WebHttpHandlerBuilder. This happens when trying to integrate with non-mock based clients."); + } httpHandlerBuilder.filter((exchange, chain) -> { CsrfWebFilter.skipExchange(exchange); return chain.filter(exchange); @@ -553,7 +559,6 @@ public final class SecurityMockServerConfigurers { configurer().afterConfigurerAdded(builder, httpHandlerBuilder, connector); } - @NullUnmarked private T configurer() { return mockAuthentication( new JwtAuthenticationToken(this.jwt, this.authoritiesConverter.convert(this.jwt))); @@ -638,10 +643,13 @@ public final class SecurityMockServerConfigurers { configurer().afterConfigureAdded(serverSpec); } - @NullUnmarked @Override public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) { + if (httpHandlerBuilder == null) { + throw new UnsupportedOperationException( + "Cannot apply Spring Security Test Support to null WebHttpHandlerBuilder. This happens when trying to integrate with non-mock based clients."); + } httpHandlerBuilder.filter((exchange, chain) -> { CsrfWebFilter.skipExchange(exchange); return chain.filter(exchange); @@ -696,8 +704,7 @@ public final class SecurityMockServerConfigurers { return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", issuedAt, expiresAt); } - @NullUnmarked - private Instant getInstant(Map attributes, String name) { + private @Nullable Instant getInstant(Map attributes, String name) { Object value = attributes.get(name); if (value == null) { return null; @@ -874,16 +881,14 @@ public final class SecurityMockServerConfigurers { private OAuth2AccessToken accessToken; - @Nullable - private OidcIdToken idToken; + private @Nullable OidcIdToken idToken; @SuppressWarnings("NullAway.Init") private OidcUserInfo userInfo; private Supplier oidcUser = this::defaultPrincipal; - @Nullable - private Collection authorities; + private @Nullable Collection authorities; private OidcLoginMutator(OAuth2AccessToken accessToken) { this.accessToken = accessToken; @@ -1027,7 +1032,6 @@ public final class SecurityMockServerConfigurers { return authorities; } - @NullUnmarked private OidcIdToken getOidcIdToken() { if (this.idToken != null) { return this.idToken; @@ -1049,13 +1053,11 @@ public final class SecurityMockServerConfigurers { * @author Josh Cummings * @since 5.3 */ - @NullUnmarked public static final class OAuth2ClientMutator implements WebTestClientConfigurer, MockServerConfigurer { private String registrationId = "test"; - @Nullable - private ClientRegistration clientRegistration; + private @Nullable ClientRegistration clientRegistration; private String principalName = "user"; @@ -1130,20 +1132,24 @@ public final class SecurityMockServerConfigurers { public void afterConfigureAdded(WebTestClient.MockServerSpec serverSpec) { } - @NullUnmarked @Override public void afterConfigurerAdded(WebTestClient.Builder builder, @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) { + if (httpHandlerBuilder == null) { + throw new UnsupportedOperationException( + "Cannot apply Spring Security Test Support to null WebHttpHandlerBuilder. This happens when trying to integrate with non-mock based clients."); + } httpHandlerBuilder.filters(addAuthorizedClientFilter()); } - @NullUnmarked private Consumer> addAuthorizedClientFilter() { OAuth2AuthorizedClient client = getClient(); return (filters) -> filters.add(0, (exchange, chain) -> { ServerOAuth2AuthorizedClientRepository authorizedClientRepository = OAuth2ClientServerTestUtils .getAuthorizedClientRepository(exchange); if (!(authorizedClientRepository instanceof TestOAuth2AuthorizedClientRepository)) { + Assert.isTrue(authorizedClientRepository != null, + "ServerOAuth2AuthorizedClientRepository cannot be null"); authorizedClientRepository = new TestOAuth2AuthorizedClientRepository(authorizedClientRepository); OAuth2ClientServerTestUtils.setAuthorizedClientRepository(exchange, authorizedClientRepository); } @@ -1153,7 +1159,6 @@ public final class SecurityMockServerConfigurers { }); } - @NullUnmarked private OAuth2AuthorizedClient getClient() { Assert.notNull(this.clientRegistration, "Please specify a ClientRegistration via one of the clientRegistration methods"); @@ -1181,18 +1186,18 @@ public final class SecurityMockServerConfigurers { private final ReactiveOAuth2AuthorizedClientManager delegate; - @Nullable - private ServerOAuth2AuthorizedClientRepository authorizedClientRepository; + private @Nullable ServerOAuth2AuthorizedClientRepository authorizedClientRepository; TestOAuth2AuthorizedClientManager(ReactiveOAuth2AuthorizedClientManager delegate) { this.delegate = delegate; } - @NullUnmarked @Override public Mono authorize(OAuth2AuthorizeRequest authorizeRequest) { ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName()); if (isEnabled(exchange)) { + Assert.isTrue(this.authorizedClientRepository != null, + "ServerOAuth2AuthorizedClientRepository not set"); return this.authorizedClientRepository.loadAuthorizedClient( authorizeRequest.getClientRegistrationId(), authorizeRequest.getPrincipal(), exchange); } @@ -1203,9 +1208,8 @@ public final class SecurityMockServerConfigurers { exchange.getAttributes().put(ENABLED_ATTR_NAME, Boolean.TRUE); } - @NullUnmarked boolean isEnabled(@Nullable ServerWebExchange exchange) { - return Boolean.TRUE.equals(exchange.getAttribute(ENABLED_ATTR_NAME)); + return exchange != null && Boolean.TRUE.equals(exchange.getAttribute(ENABLED_ATTR_NAME)); } } @@ -1223,8 +1227,7 @@ public final class SecurityMockServerConfigurers { private final ServerOAuth2AuthorizedClientRepository delegate; - @NullUnmarked - TestOAuth2AuthorizedClientRepository(@Nullable ServerOAuth2AuthorizedClientRepository delegate) { + TestOAuth2AuthorizedClientRepository(ServerOAuth2AuthorizedClientRepository delegate) { this.delegate = delegate; } @@ -1232,7 +1235,8 @@ public final class SecurityMockServerConfigurers { public Mono loadAuthorizedClient(String clientRegistrationId, Authentication principal, ServerWebExchange exchange) { if (isEnabled(exchange)) { - return Mono.just(exchange.getAttribute(TOKEN_ATTR_NAME)); + T attr = exchange.getAttribute(TOKEN_ATTR_NAME); + return Mono.justOrEmpty(attr); } return this.delegate.loadAuthorizedClient(clientRegistrationId, principal, exchange); } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index 110e496e48..a961512bd9 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -40,7 +40,6 @@ import java.util.stream.Collectors; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; @@ -399,7 +398,6 @@ public final class SecurityMockMvcRequestPostProcessors { * @return the {@link OidcLoginRequestPostProcessor} for additional customization * @since 5.3 */ - @NullUnmarked public static OAuth2LoginRequestPostProcessor oauth2Login() { OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", null, null, Collections.singleton("read")); @@ -428,7 +426,6 @@ public final class SecurityMockMvcRequestPostProcessors { * @return the {@link OidcLoginRequestPostProcessor} for additional customization * @since 5.3 */ - @NullUnmarked public static OidcLoginRequestPostProcessor oidcLogin() { OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", null, null, Collections.singleton("read")); @@ -517,11 +514,11 @@ public final class SecurityMockMvcRequestPostProcessors { private CsrfRequestPostProcessor() { } - @NullUnmarked @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request); CsrfTokenRequestHandler handler = WebTestUtils.getCsrfTokenRequestHandler(request); + Assert.isTrue(handler != null, "No CsrfTokenRequestHandler found"); if (!(repository instanceof TestCsrfTokenRepository)) { repository = new TestCsrfTokenRepository(new HttpSessionCsrfTokenRepository()); WebTestUtils.setCsrfTokenRepository(request, repository); @@ -531,6 +528,9 @@ public final class SecurityMockMvcRequestPostProcessors { DeferredCsrfToken deferredCsrfToken = repository.loadDeferredToken(request, response); handler.handle(request, response, deferredCsrfToken); CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); + if (token == null) { + return request; + } String tokenValue = this.useInvalidToken ? INVALID_TOKEN_VALUE : token.getToken(); if (this.asHeader) { request.addHeader(token.getHeaderName(), tokenValue); @@ -1135,7 +1135,6 @@ public final class SecurityMockMvcRequestPostProcessors { return this; } - @NullUnmarked @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { CsrfFilter.skipRequest(request); @@ -1262,8 +1261,7 @@ public final class SecurityMockMvcRequestPostProcessors { return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", issuedAt, expiresAt); } - @NullUnmarked - private Instant getInstant(Map attributes, String name) { + private @Nullable Instant getInstant(Map attributes, String name) { Object value = attributes.get(name); if (value == null) { return null; @@ -1534,7 +1532,6 @@ public final class SecurityMockMvcRequestPostProcessors { return authorities; } - @NullUnmarked private OidcIdToken getOidcIdToken() { if (this.idToken != null) { return this.idToken; @@ -1556,7 +1553,6 @@ public final class SecurityMockMvcRequestPostProcessors { * @author Josh Cummings * @since 5.3 */ - @NullUnmarked public static final class OAuth2ClientRequestPostProcessor implements RequestPostProcessor { private String registrationId = "test"; @@ -1621,7 +1617,6 @@ public final class SecurityMockMvcRequestPostProcessors { return this; } - @NullUnmarked @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { if (this.clientRegistration == null) { @@ -1632,6 +1627,8 @@ public final class SecurityMockMvcRequestPostProcessors { this.accessToken); OAuth2AuthorizedClientRepository authorizedClientRepository = OAuth2ClientServletTestUtils .getAuthorizedClientRepository(request); + Assert.isTrue(authorizedClientRepository != null, + "Could not find OAuth2AuthorizedClientRepository on the request"); if (!(authorizedClientRepository instanceof TestOAuth2AuthorizedClientRepository)) { authorizedClientRepository = new TestOAuth2AuthorizedClientRepository(authorizedClientRepository); OAuth2ClientServletTestUtils.setAuthorizedClientRepository(request, authorizedClientRepository); @@ -1668,11 +1665,10 @@ public final class SecurityMockMvcRequestPostProcessors { this.delegate = delegate; } - @NullUnmarked @Override public @Nullable OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) { HttpServletRequest request = authorizeRequest.getAttribute(HttpServletRequest.class.getName()); - if (isEnabled(request)) { + if (this.authorizedClientRepository != null && isEnabled(request)) { return this.authorizedClientRepository.loadAuthorizedClient( authorizeRequest.getClientRegistrationId(), authorizeRequest.getPrincipal(), request); } @@ -1683,9 +1679,8 @@ public final class SecurityMockMvcRequestPostProcessors { request.setAttribute(ENABLED_ATTR_NAME, Boolean.TRUE); } - @NullUnmarked boolean isEnabled(@Nullable HttpServletRequest request) { - return Boolean.TRUE.equals(request.getAttribute(ENABLED_ATTR_NAME)); + return request != null && Boolean.TRUE.equals(request.getAttribute(ENABLED_ATTR_NAME)); } } @@ -1703,8 +1698,7 @@ public final class SecurityMockMvcRequestPostProcessors { private final OAuth2AuthorizedClientRepository delegate; - @NullUnmarked - TestOAuth2AuthorizedClientRepository(@Nullable OAuth2AuthorizedClientRepository delegate) { + TestOAuth2AuthorizedClientRepository(OAuth2AuthorizedClientRepository delegate) { this.delegate = delegate; } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java b/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java index 1b1e73badd..fe2fcb6f85 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/response/SecurityMockMvcResultMatchers.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -107,7 +106,6 @@ public final class SecurityMockMvcResultMatchers { AuthenticatedMatcher() { } - @NullUnmarked @Override public void match(MvcResult result) { SecurityContext context = load(result); diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/setup/SecurityMockMvcConfigurer.java b/test/src/main/java/org/springframework/security/test/web/servlet/setup/SecurityMockMvcConfigurer.java index 83bd0bd330..8886311671 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/setup/SecurityMockMvcConfigurer.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/setup/SecurityMockMvcConfigurer.java @@ -21,10 +21,10 @@ import java.io.IOException; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; -import org.jspecify.annotations.NullUnmarked; import org.springframework.security.config.BeanIds; import org.springframework.test.web.servlet.request.RequestPostProcessor; @@ -67,7 +67,6 @@ final class SecurityMockMvcConfigurer extends MockMvcConfigurerAdapter { builder.addFilters(this.delegateFilter); } - @NullUnmarked @Override public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, WebApplicationContext context) { @@ -79,7 +78,9 @@ final class SecurityMockMvcConfigurer extends MockMvcConfigurerAdapter { () -> "springSecurityFilterChain cannot be null. Ensure a Bean with the name " + securityBeanId + " implementing Filter is present or inject the Filter to be used."); // This is used by other test support to obtain the FilterChainProxy - context.getServletContext().setAttribute(BeanIds.SPRING_SECURITY_FILTER_CHAIN, getSpringSecurityFilterChain()); + ServletContext servletContext = context.getServletContext(); + Assert.notNull(servletContext, "ServletContext must not be null"); + servletContext.setAttribute(BeanIds.SPRING_SECURITY_FILTER_CHAIN, getSpringSecurityFilterChain()); return testSecurityContext(); } diff --git a/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java b/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java index 77a64eb246..eb23d4b946 100644 --- a/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java +++ b/test/src/main/java/org/springframework/security/test/web/support/WebTestUtils.java @@ -21,7 +21,6 @@ import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -37,6 +36,7 @@ import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; @@ -66,15 +66,19 @@ public abstract class WebTestUtils { * @return the {@link SecurityContextRepository} for the specified * {@link HttpServletRequest} */ - @NullUnmarked public static SecurityContextRepository getSecurityContextRepository(HttpServletRequest request) { SecurityContextPersistenceFilter filter = findFilter(request, SecurityContextPersistenceFilter.class); if (filter != null) { - return (SecurityContextRepository) ReflectionTestUtils.getField(filter, "repo"); + SecurityContextRepository repo = (SecurityContextRepository) ReflectionTestUtils.getField(filter, "repo"); + Assert.notNull(repo, "SecurityContextRepository must not be null"); + return repo; } SecurityContextHolderFilter holderFilter = findFilter(request, SecurityContextHolderFilter.class); if (holderFilter != null) { - return (SecurityContextRepository) ReflectionTestUtils.getField(holderFilter, "securityContextRepository"); + SecurityContextRepository securityContextRepository = (SecurityContextRepository) ReflectionTestUtils + .getField(holderFilter, "securityContextRepository"); + Assert.notNull(securityContextRepository, "SecurityContextRepository must not be null"); + return securityContextRepository; } return DEFAULT_CONTEXT_REPO; } diff --git a/web/src/main/java/org/springframework/security/web/server/ObservationWebFilterChainDecorator.java b/web/src/main/java/org/springframework/security/web/server/ObservationWebFilterChainDecorator.java index 19658803a4..3ef13ad257 100644 --- a/web/src/main/java/org/springframework/security/web/server/ObservationWebFilterChainDecorator.java +++ b/web/src/main/java/org/springframework/security/web/server/ObservationWebFilterChainDecorator.java @@ -26,7 +26,6 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; @@ -69,14 +68,14 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP return new ObservationWebFilterChain(wrapSecured(original)::filter, wrap(filters)); } - private static AroundWebFilterObservation observation(ServerWebExchange exchange) { - AroundWebFilterObservation observation = exchange.getAttribute(ATTRIBUTE); - return (observation != null) ? observation : AroundWebFilterObservation.NOOP; + private static @Nullable AroundWebFilterObservation observation(ServerWebExchange exchange) { + return exchange.getAttribute(ATTRIBUTE); } private WebFilterChain wrapSecured(WebFilterChain original) { return (exchange) -> Mono.deferContextual((contextView) -> { AroundWebFilterObservation parent = observation(exchange); + Assert.notNull(parent, "AroundWebFilterObservation parent cannot be null"); Observation parentObservation = contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null); Observation observation = Observation.createNotStarted(SECURED_OBSERVATION_NAME, this.registry) .contextualName("secured request") @@ -203,6 +202,7 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP private Mono wrapFilter(ServerWebExchange exchange, WebFilterChain chain) { AroundWebFilterObservation parent = observation(exchange); + Assert.notNull(parent, "ObservationWebFilter parent is required"); if (parent.before().getContext() instanceof WebFilterChainObservationContext parentBefore) { parentBefore.setChainSize(this.size); parentBefore.setFilterName(this.name); @@ -575,7 +575,7 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP private final String filterSection; - @Nullable private String filterName; + private @Nullable String filterName; private int chainPosition; diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java index 4d18768b34..ab8b7ebf18 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java @@ -46,7 +46,6 @@ import com.webauthn4j.data.client.challenge.DefaultChallenge; import com.webauthn4j.data.extension.authenticator.AuthenticationExtensionAuthenticatorOutput; import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput; import com.webauthn4j.server.ServerProperty; -import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -358,7 +357,6 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe .build(); } - @NullUnmarked private List findCredentialRecords(@Nullable Authentication authentication) { if (!this.trustResolver.isAuthenticated(authentication)) { return Collections.emptyList();