Ver código fonte

Update to Reactor 2025.0.0-SNAPSHOT

To prepare for the release we should update to Reactor
2025.0.0-SNAPSHOT to fix any issues that are present.

Closes gh-18041
Rob Winch 1 dia atrás
pai
commit
b864be92d8
18 arquivos alterados com 43 adições e 20 exclusões
  1. 1 0
      core/src/main/java/org/springframework/security/authentication/ott/reactive/InMemoryReactiveOneTimeTokenService.java
  2. 2 1
      core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java
  3. 4 2
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java
  4. 9 1
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java
  5. 2 1
      core/src/main/java/org/springframework/security/authorization/method/ReactiveAuthenticationUtils.java
  6. 1 1
      core/src/main/java/org/springframework/security/authorization/method/ReactiveMethodInvocationUtils.java
  7. 2 1
      core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java
  8. 1 1
      gradle/libs.versions.toml
  9. 2 1
      messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java
  10. 2 2
      rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java
  11. 2 1
      rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java
  12. 2 1
      web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java
  13. 4 2
      web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java
  14. 1 0
      web/src/main/java/org/springframework/security/web/server/authentication/ott/DefaultServerGenerateOneTimeTokenRequestResolver.java
  15. 2 1
      web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java
  16. 2 2
      web/src/main/java/org/springframework/security/web/server/context/SecurityContextServerWebExchange.java
  17. 2 1
      web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java
  18. 2 1
      web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java

+ 1 - 0
core/src/main/java/org/springframework/security/authentication/ott/reactive/InMemoryReactiveOneTimeTokenService.java

@@ -43,6 +43,7 @@ public final class InMemoryReactiveOneTimeTokenService implements ReactiveOneTim
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<OneTimeToken> consume(OneTimeTokenAuthenticationToken authenticationToken) {
 		return Mono.just(authenticationToken).mapNotNull(this.oneTimeTokenService::consume);
 	}

+ 2 - 1
core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java

@@ -58,9 +58,10 @@ public final class ObservationReactiveAuthorizationManager<T>
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<AuthorizationResult> authorize(Mono<Authentication> authentication, T object) {
 		AuthorizationObservationContext<T> context = new AuthorizationObservationContext<>(object);
-		Mono<Authentication> wrapped = authentication.map((auth) -> {
+		Mono<Authentication> wrapped = authentication.mapNotNull((auth) -> {
 			context.setAuthentication(auth);
 			return context.getAuthentication();
 		});

+ 4 - 2
core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java

@@ -588,12 +588,14 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
 			return null;
 		}
 
+		@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 		private Mono<?> proxyMono(AuthorizationProxyFactory proxyFactory, Mono<?> mono) {
-			return mono.map(proxyFactory::proxy);
+			return mono.mapNotNull(proxyFactory::proxy);
 		}
 
+		@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 		private Flux<?> proxyFlux(AuthorizationProxyFactory proxyFactory, Flux<?> flux) {
-			return flux.map(proxyFactory::proxy);
+			return flux.mapNotNull(proxyFactory::proxy);
 		}
 
 	}

+ 9 - 1
core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java

@@ -120,6 +120,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
 						+ "(for example, a Mono or Flux) or the function must be a Kotlin coroutine "
 						+ "in order to support Reactor Context");
 		Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
+		@SuppressWarnings("NullAway") // Dataflow analysis limitation
 		Function<Signal<?>, Mono<?>> postAuthorize = (signal) -> {
 			if (signal.isOnComplete()) {
 				return Mono.empty();
@@ -130,12 +131,16 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
 			if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
 				return postProcess(denied, mi);
 			}
+			// getThrowable must be non-null because hasError() is true
 			return Mono.error(signal.getThrowable());
 		};
 		ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type);
 		if (hasFlowReturnType) {
 			if (isSuspendingFunction) {
 				Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
+				if (publisher == null) {
+					return Flux.empty();
+				}
 				return Flux.from(publisher).materialize().flatMap(postAuthorize);
 			}
 			else {
@@ -148,6 +153,9 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
 			}
 		}
 		Publisher<?> publisher = ReactiveMethodInvocationUtils.proceed(mi);
+		if (publisher == null) {
+			return Flux.empty();
+		}
 		if (isMultiValue(type, adapter)) {
 			Flux<?> flux = Flux.from(publisher).materialize().flatMap(postAuthorize);
 			return (adapter != null) ? adapter.fromPublisher(flux) : flux;
@@ -173,7 +181,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
 
 	private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) {
 		if (decision.isGranted()) {
-			return Mono.just(methodInvocationResult.getResult());
+			return Mono.justOrEmpty(methodInvocationResult.getResult());
 		}
 		return Mono.fromSupplier(() -> {
 			if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {

+ 2 - 1
core/src/main/java/org/springframework/security/authorization/method/ReactiveAuthenticationUtils.java

@@ -35,9 +35,10 @@ final class ReactiveAuthenticationUtils {
 	private static final Authentication ANONYMOUS = new AnonymousAuthenticationToken("key", "anonymous",
 			AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
 
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	static Mono<Authentication> getAuthentication() {
 		return ReactiveSecurityContextHolder.getContext()
-			.map(SecurityContext::getAuthentication)
+			.mapNotNull(SecurityContext::getAuthentication)
 			.defaultIfEmpty(ANONYMOUS);
 	}
 

+ 1 - 1
core/src/main/java/org/springframework/security/authorization/method/ReactiveMethodInvocationUtils.java

@@ -28,7 +28,7 @@ import reactor.core.Exceptions;
  */
 final class ReactiveMethodInvocationUtils {
 
-	static <T> @Nullable T proceed(MethodInvocation mi) {
+	static @Nullable <T> T proceed(MethodInvocation mi) {
 		try {
 			return (T) mi.proceed();
 		}

+ 2 - 1
core/src/main/java/org/springframework/security/core/session/InMemoryReactiveSessionRegistry.java

@@ -50,9 +50,10 @@ public class InMemoryReactiveSessionRegistry implements ReactiveSessionRegistry
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Flux<ReactiveSessionInformation> getAllSessions(Object principal) {
 		return Flux.fromIterable(this.sessionIdsByPrincipal.getOrDefault(principal, Collections.emptySet()))
-			.map(this.sessionById::get);
+			.mapNotNull(this.sessionById::get);
 	}
 
 	@Override

+ 1 - 1
gradle/libs.versions.toml

@@ -30,7 +30,7 @@ commons-collections = "commons-collections:commons-collections:3.2.2"
 io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
 io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.11"
 io-mockk = "io.mockk:mockk:1.14.6"
-io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-M7"
+io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0-SNAPSHOT"
 io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
 io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
 io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }

+ 2 - 1
messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java

@@ -138,11 +138,12 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<Object> resolveArgument(MethodParameter parameter, Message<?> message) {
 		ReactiveAdapter adapter = this.adapterRegistry.getAdapter(parameter.getParameterType());
 		// @formatter:off
 		return ReactiveSecurityContextHolder.getContext()
-				.map(SecurityContext::getAuthentication)
+				.mapNotNull(SecurityContext::getAuthentication)
 				.flatMap((a) -> {
 					Object p = resolvePrincipal(parameter, a.getPrincipal());
 					Mono<Object> principal = Mono.justOrEmpty(p);

+ 2 - 2
rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java

@@ -55,10 +55,10 @@ public class AuthorizationPayloadInterceptor implements PayloadInterceptor, Orde
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<Void> intercept(PayloadExchange exchange, PayloadInterceptorChain chain) {
 		return ReactiveSecurityContextHolder.getContext()
-			.filter((c) -> c.getAuthentication() != null)
-			.map(SecurityContext::getAuthentication)
+			.mapNotNull(SecurityContext::getAuthentication)
 			.switchIfEmpty(Mono.error(() -> new AuthenticationCredentialsNotFoundException(
 					"An Authentication (possibly AnonymousAuthenticationToken) is required.")))
 			.as((authentication) -> this.authorizationManager.verify(authentication, exchange))

+ 2 - 1
rsocket/src/main/java/org/springframework/security/rsocket/authorization/PayloadExchangeMatcherReactiveAuthorizationManager.java

@@ -52,12 +52,13 @@ public final class PayloadExchangeMatcherReactiveAuthorizationManager
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<AuthorizationResult> authorize(Mono<Authentication> authentication, PayloadExchange exchange) {
 		return Flux.fromIterable(this.mappings)
 			.concatMap((mapping) -> mapping.getMatcher()
 				.matches(exchange)
 				.filter(PayloadExchangeMatcher.MatchResult::isMatch)
-				.map(MatchResult::getVariables)
+				.mapNotNull(MatchResult::getVariables)
 				.flatMap((variables) -> mapping.getEntry()
 					.authorize(authentication, new PayloadExchangeAuthorizationContext(exchange, variables))))
 			.next()

+ 2 - 1
web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java

@@ -83,11 +83,12 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
 			ServerWebExchange exchange) {
 		ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameter.getParameterType());
 		return ReactiveSecurityContextHolder.getContext()
-			.map(SecurityContext::getAuthentication)
+			.mapNotNull(SecurityContext::getAuthentication)
 			.flatMap((authentication) -> {
 				Mono<Object> principal = Mono.justOrEmpty(resolvePrincipal(parameter, authentication.getPrincipal()));
 				return (adapter != null) ? Mono.just(adapter.fromPublisher(principal)) : principal;

+ 4 - 2
web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java

@@ -176,11 +176,12 @@ public class SwitchUserWebFilter implements WebFilter {
 	 * @throws AuthenticationCredentialsNotFoundException If the target user can not be
 	 * found by username
 	 */
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	protected Mono<Authentication> switchUser(WebFilterExchange webFilterExchange) {
 		return this.switchUserMatcher.matches(webFilterExchange.getExchange())
 			.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
 			.flatMap((matchResult) -> ReactiveSecurityContextHolder.getContext())
-			.map(SecurityContext::getAuthentication)
+			.mapNotNull(SecurityContext::getAuthentication)
 			.flatMap((currentAuthentication) -> {
 				String username = getUsername(webFilterExchange.getExchange());
 				return attemptSwitchUser(currentAuthentication, username);
@@ -197,11 +198,12 @@ public class SwitchUserWebFilter implements WebFilter {
 	 * <code>Authentication</code> associated with this request or the user is not
 	 * switched.
 	 */
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	protected Mono<Authentication> exitSwitchUser(WebFilterExchange webFilterExchange) {
 		return this.exitUserMatcher.matches(webFilterExchange.getExchange())
 			.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
 			.flatMap((matchResult) -> ReactiveSecurityContextHolder.getContext()
-				.map(SecurityContext::getAuthentication)
+				.mapNotNull(SecurityContext::getAuthentication)
 				.switchIfEmpty(Mono.error(this::noCurrentUserException)))
 			.map(this::attemptExitUser);
 	}

+ 1 - 0
web/src/main/java/org/springframework/security/web/server/authentication/ott/DefaultServerGenerateOneTimeTokenRequestResolver.java

@@ -41,6 +41,7 @@ public final class DefaultServerGenerateOneTimeTokenRequestResolver
 	private Duration expiresIn = DEFAULT_EXPIRES_IN;
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<GenerateOneTimeTokenRequest> resolve(ServerWebExchange exchange) {
 		// @formatter:off
 		return exchange.getFormData()

+ 2 - 1
web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java

@@ -45,10 +45,11 @@ public class AuthorizationWebFilter implements WebFilter {
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
 		return ReactiveSecurityContextHolder.getContext()
 			.filter((c) -> c.getAuthentication() != null)
-			.map(SecurityContext::getAuthentication)
+			.mapNotNull(SecurityContext::getAuthentication)
 			.as((authentication) -> this.authorizationManager.verify(authentication, exchange))
 			.doOnSuccess((it) -> logger.debug("Authorization successful"))
 			.doOnError(AccessDeniedException.class,

+ 2 - 2
web/src/main/java/org/springframework/security/web/server/context/SecurityContextServerWebExchange.java

@@ -42,9 +42,9 @@ public class SecurityContextServerWebExchange extends ServerWebExchangeDecorator
 	}
 
 	@Override
-	@SuppressWarnings("unchecked")
+	@SuppressWarnings({ "unchecked", "NullAway" }) // https://github.com/uber/NullAway/issues/1290
 	public <T extends Principal> Mono<T> getPrincipal() {
-		return this.context.map((context) -> (T) context.getAuthentication());
+		return this.context.mapNotNull((context) -> (T) context.getAuthentication());
 	}
 
 }

+ 2 - 1
web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRequestAttributeHandler.java

@@ -67,6 +67,7 @@ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRe
 		this.isTokenFromMultipartDataEnabled = tokenFromMultipartDataEnabled;
 	}
 
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	private Mono<String> tokenFromMultipartData(ServerWebExchange exchange, CsrfToken expected) {
 		if (!this.isTokenFromMultipartDataEnabled) {
 			return Mono.empty();
@@ -78,7 +79,7 @@ public class ServerCsrfTokenRequestAttributeHandler implements ServerCsrfTokenRe
 			return Mono.empty();
 		}
 		return exchange.getMultipartData()
-			.map((d) -> d.getFirst(expected.getParameterName()))
+			.mapNotNull((d) -> d.getFirst(expected.getParameterName()))
 			.cast(FormFieldPart.class)
 			.map(FormFieldPart::value);
 	}

+ 2 - 1
web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java

@@ -71,10 +71,11 @@ public class WebSessionServerCsrfTokenRepository implements ServerCsrfTokenRepos
 	}
 
 	@Override
+	@SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1290
 	public Mono<CsrfToken> loadToken(ServerWebExchange exchange) {
 		return exchange.getSession()
 			.filter((session) -> session.getAttributes().containsKey(this.sessionAttributeName))
-			.map((session) -> session.getAttribute(this.sessionAttributeName));
+			.mapNotNull((session) -> session.getAttribute(this.sessionAttributeName));
 	}
 
 	/**