Преглед изворни кода

Enable Null checking in spring-security-web via JSpecify

Closes gh-16882
Rob Winch пре 1 недеља
родитељ
комит
be64c67af5
27 измењених фајлова са 237 додато и 36 уклоњено
  1. 4 0
      messaging/spring-security-messaging.gradle
  2. 7 2
      messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java
  3. 1 1
      messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageAuthorizationContextSecurityExpressionHandler.java
  4. 3 1
      messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java
  5. 3 1
      messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java
  6. 23 0
      messaging/src/main/java/org/springframework/security/messaging/access/expression/package-info.java
  7. 2 1
      messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java
  8. 4 2
      messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java
  9. 2 1
      messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java
  10. 4 3
      messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java
  11. 23 0
      messaging/src/main/java/org/springframework/security/messaging/access/intercept/package-info.java
  12. 5 2
      messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java
  13. 6 3
      messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java
  14. 4 1
      messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextPropagationChannelInterceptor.java
  15. 24 0
      messaging/src/main/java/org/springframework/security/messaging/context/package-info.java
  16. 7 4
      messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java
  17. 11 7
      messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java
  18. 23 0
      messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/package-info.java
  19. 1 1
      messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java
  20. 23 0
      messaging/src/main/java/org/springframework/security/messaging/util/matcher/package-info.java
  21. 3 1
      messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfChannelInterceptor.java
  22. 3 1
      messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfTokenUtils.java
  23. 23 0
      messaging/src/main/java/org/springframework/security/messaging/web/csrf/package-info.java
  24. 2 1
      messaging/src/main/java/org/springframework/security/messaging/web/socket/server/CsrfTokenHandshakeInterceptor.java
  25. 23 0
      messaging/src/main/java/org/springframework/security/messaging/web/socket/server/package-info.java
  26. 1 1
      messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java
  27. 2 2
      messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java

+ 4 - 0
messaging/spring-security-messaging.gradle

@@ -1,3 +1,7 @@
+plugins {
+	id 'security-nullability'
+}
+
 apply plugin: 'io.spring.convention.spring-module'
 
 dependencies {

+ 7 - 2
messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java

@@ -20,6 +20,7 @@ import java.util.function.Supplier;
 
 import org.jspecify.annotations.Nullable;
 
+import org.springframework.expression.BeanResolver;
 import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.messaging.Message;
@@ -49,12 +50,16 @@ public class DefaultMessageSecurityExpressionHandler<T> extends AbstractSecurity
 			Message<T> message) {
 		MessageSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, message);
 		StandardEvaluationContext ctx = new StandardEvaluationContext(root);
-		ctx.setBeanResolver(getBeanResolver());
+		BeanResolver beanResolver = getBeanResolver();
+		if (beanResolver != null) {
+			// https://github.com/spring-projects/spring-framework/issues/35371
+			ctx.setBeanResolver(beanResolver);
+		}
 		return ctx;
 	}
 
 	@Override
-	protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
+	protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
 			Message<T> invocation) {
 		return createSecurityExpressionRoot(() -> authentication, invocation);
 	}

+ 1 - 1
messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageAuthorizationContextSecurityExpressionHandler.java

@@ -55,7 +55,7 @@ public final class MessageAuthorizationContextSecurityExpressionHandler
 	}
 
 	@Override
-	public EvaluationContext createEvaluationContext(Authentication authentication,
+	public EvaluationContext createEvaluationContext(@Nullable Authentication authentication,
 			MessageAuthorizationContext<?> message) {
 		return createEvaluationContext(() -> authentication, message);
 	}

+ 3 - 1
messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.access.expression;
 
 import java.util.Map;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.Expression;
 import org.springframework.messaging.Message;
@@ -60,7 +62,7 @@ class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationCon
 	}
 
 	@Override
-	public String getAttribute() {
+	public @Nullable String getAttribute() {
 		return null;
 	}
 

+ 3 - 1
messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.access.expression;
 
 import java.util.Collection;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.expression.EvaluationContext;
 import org.springframework.messaging.Message;
 import org.springframework.security.access.AccessDecisionVoter;
@@ -60,7 +62,7 @@ public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>
 		return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED;
 	}
 
-	private MessageExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
+	private @Nullable MessageExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
 		for (ConfigAttribute attribute : attributes) {
 			if (attribute instanceof MessageExpressionConfigAttribute) {
 				return (MessageExpressionConfigAttribute) attribute;

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/access/expression/package-info.java

@@ -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.
+ */
+
+/**
+ * Security expression support for {@link org.springframework.messaging.Message}.
+ */
+@NullMarked
+package org.springframework.security.messaging.access.expression;
+
+import org.jspecify.annotations.NullMarked;

+ 2 - 1
messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java

@@ -20,6 +20,7 @@ import java.util.function.Supplier;
 
 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.messaging.Message;
@@ -110,7 +111,7 @@ public final class AuthorizationChannelInterceptor implements ChannelInterceptor
 
 		@Override
 		public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
-				AuthorizationResult result) {
+				@Nullable AuthorizationResult result) {
 
 		}
 

+ 4 - 2
messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java

@@ -16,6 +16,8 @@
 
 package org.springframework.security.messaging.access.intercept;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.support.ChannelInterceptor;
@@ -83,7 +85,7 @@ public final class ChannelSecurityInterceptor extends AbstractSecurityIntercepto
 	}
 
 	@Override
-	public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
+	public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, @Nullable Exception ex) {
 		InterceptorStatusToken token = clearToken();
 		finallyInvocation(token);
 	}
@@ -99,7 +101,7 @@ public final class ChannelSecurityInterceptor extends AbstractSecurityIntercepto
 	}
 
 	@Override
-	public void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex) {
+	public void afterReceiveCompletion(@Nullable Message<?> message, MessageChannel channel, @Nullable Exception ex) {
 	}
 
 	private InterceptorStatusToken clearToken() {

+ 2 - 1
messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java

@@ -17,6 +17,7 @@
 package org.springframework.security.messaging.access.intercept;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -61,7 +62,7 @@ public final class DefaultMessageSecurityMetadataSource implements MessageSecuri
 				return entry.getValue();
 			}
 		}
-		return null;
+		return Collections.emptyList();
 	}
 
 	@Override

+ 4 - 3
messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java

@@ -55,7 +55,7 @@ public final class MessageMatcherDelegatingAuthorizationManager implements Autho
 	}
 
 	@Override
-	public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
+	public @Nullable AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication,
 			Message<?> message) {
 		if (this.logger.isTraceEnabled()) {
 			this.logger.trace(LogMessage.format("Authorizing message"));
@@ -75,7 +75,8 @@ public final class MessageMatcherDelegatingAuthorizationManager implements Autho
 		return null;
 	}
 
-	private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> matcher, Message<?> message) {
+	private @Nullable MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> matcher,
+			Message<?> message) {
 		MessageMatcher.MatchResult matchResult = matcher.matcher((Message) message);
 		if (!matchResult.isMatch()) {
 			return null;
@@ -179,7 +180,7 @@ public final class MessageMatcherDelegatingAuthorizationManager implements Autho
 		 * @return the {@link Builder.Constraint} that is associated to the
 		 * {@link MessageMatcher}
 		 */
-		private Builder.Constraint simpDestMatchers(SimpMessageType type, String... patterns) {
+		private Builder.Constraint simpDestMatchers(@Nullable SimpMessageType type, String... patterns) {
 			List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
 			for (String pattern : patterns) {
 				MessageMatcher<Object> matcher = this.messageMatcherBuilder.matcher(type, pattern);

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/access/intercept/package-info.java

@@ -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.
+ */
+
+/**
+ * Authorization support for {@link org.springframework.messaging.Message}.
+ */
+@NullMarked
+package org.springframework.security.messaging.access.intercept;
+
+import org.jspecify.annotations.NullMarked;

+ 5 - 2
messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.context;
 
 import java.lang.annotation.Annotation;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.core.MethodParameter;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.core.annotation.MergedAnnotations;
@@ -110,13 +112,14 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 	}
 
 	@Override
-	public Object resolveArgument(MethodParameter parameter, Message<?> message) {
+	public @Nullable Object resolveArgument(MethodParameter parameter, Message<?> message) {
 		Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
 		if (authentication == null) {
 			return null;
 		}
 		Object principal = authentication.getPrincipal();
 		AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter);
+		Assert.notNull(authPrincipal, "AuthenticationPrincipal must not be null. Run supports first");
 		String expressionToParse = authPrincipal.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
@@ -165,7 +168,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) {
+	private @Nullable AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) {
 		if (this.useAnnotationTemplate) {
 			return this.scanner.scan(parameter.getParameter());
 		}

+ 6 - 3
messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.context;
 
 import java.util.Stack;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.MessageHandler;
@@ -96,7 +98,7 @@ public final class SecurityContextChannelInterceptor implements ExecutorChannelI
 	}
 
 	@Override
-	public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) {
+	public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, @Nullable Exception ex) {
 		cleanup();
 	}
 
@@ -107,7 +109,8 @@ public final class SecurityContextChannelInterceptor implements ExecutorChannelI
 	}
 
 	@Override
-	public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
+	public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler,
+			@Nullable Exception ex) {
 		cleanup();
 	}
 
@@ -131,7 +134,7 @@ public final class SecurityContextChannelInterceptor implements ExecutorChannelI
 		this.securityContextHolderStrategy.setContext(context);
 	}
 
-	private Authentication getAuthentication(Object user) {
+	private Authentication getAuthentication(@Nullable Object user) {
 		if ((user instanceof Authentication)) {
 			return (Authentication) user;
 		}

+ 4 - 1
messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextPropagationChannelInterceptor.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.context;
 
 import java.util.Stack;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.MessageHandler;
@@ -121,7 +123,8 @@ public final class SecurityContextPropagationChannelInterceptor implements Execu
 	}
 
 	@Override
-	public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
+	public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler,
+			@Nullable Exception ex) {
 		cleanup();
 	}
 

+ 24 - 0
messaging/src/main/java/org/springframework/security/messaging/context/package-info.java

@@ -0,0 +1,24 @@
+/*
+ * 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 establishing the
+ * {@link org.springframework.security.core.context.SecurityContext} within messaging.
+ */
+@NullMarked
+package org.springframework.security.messaging.context;
+
+import org.jspecify.annotations.NullMarked;

+ 7 - 4
messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java

@@ -18,6 +18,8 @@ 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;
 
@@ -108,7 +110,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 
 	private boolean useAnnotationTemplate = false;
 
-	private BeanResolver beanResolver;
+	private @Nullable BeanResolver beanResolver;
 
 	private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
 
@@ -149,7 +151,8 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 		// @formatter:on
 	}
 
-	private Object resolvePrincipal(MethodParameter parameter, Object principal) {
+	@NullUnmarked
+	private @Nullable Object resolvePrincipal(MethodParameter parameter, @Nullable Object principal) {
 		AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter);
 		String expressionToParse = authPrincipal.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
@@ -169,7 +172,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 		return principal;
 	}
 
-	private boolean isInvalidType(MethodParameter parameter, Object principal) {
+	private boolean isInvalidType(MethodParameter parameter, @Nullable Object principal) {
 		if (principal == null) {
 			return false;
 		}
@@ -206,7 +209,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) {
+	private @Nullable AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) {
 		if (this.useAnnotationTemplate) {
 			return this.scanner.scan(parameter.getParameter());
 		}

+ 11 - 7
messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java

@@ -18,6 +18,7 @@ package org.springframework.security.messaging.handler.invocation.reactive;
 
 import java.lang.annotation.Annotation;
 
+import org.jspecify.annotations.Nullable;
 import org.reactivestreams.Publisher;
 import reactor.core.publisher.Mono;
 
@@ -106,7 +107,7 @@ public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgu
 
 	private boolean useAnnotationTemplate = false;
 
-	private BeanResolver beanResolver;
+	private @Nullable BeanResolver beanResolver;
 
 	private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
 
@@ -159,7 +160,7 @@ public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgu
 		// @formatter:on
 	}
 
-	private Object resolveSecurityContext(MethodParameter parameter, Object securityContext) {
+	private @Nullable Object resolveSecurityContext(MethodParameter parameter, Object securityContext) {
 		CurrentSecurityContext contextAnno = findMethodAnnotation(parameter);
 		if (contextAnno != null) {
 			return resolveSecurityContextFromAnnotation(contextAnno, parameter, securityContext);
@@ -167,14 +168,17 @@ public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgu
 		return securityContext;
 	}
 
-	private Object resolveSecurityContextFromAnnotation(CurrentSecurityContext contextAnno, MethodParameter parameter,
-			Object securityContext) {
+	private @Nullable Object resolveSecurityContextFromAnnotation(CurrentSecurityContext contextAnno,
+			MethodParameter parameter, Object securityContext) {
 		String expressionToParse = contextAnno.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
 			context.setRootObject(securityContext);
 			context.setVariable("this", securityContext);
-			context.setBeanResolver(this.beanResolver);
+			if (this.beanResolver != null) {
+				// https://github.com/spring-projects/spring-framework/issues/35371
+				context.setBeanResolver(this.beanResolver);
+			}
 			Expression expression = this.parser.parseExpression(expressionToParse);
 			securityContext = expression.getValue(context);
 		}
@@ -187,7 +191,7 @@ public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgu
 		return securityContext;
 	}
 
-	private boolean isInvalidType(MethodParameter parameter, Object value) {
+	private boolean isInvalidType(MethodParameter parameter, @Nullable Object value) {
 		if (value == null) {
 			return false;
 		}
@@ -223,7 +227,7 @@ public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgu
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private CurrentSecurityContext findMethodAnnotation(MethodParameter parameter) {
+	private @Nullable CurrentSecurityContext findMethodAnnotation(MethodParameter parameter) {
 		if (this.useAnnotationTemplate) {
 			return this.scanner.scan(parameter.getParameter());
 		}

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/package-info.java

@@ -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 resolving security related arguments.
+ */
+@NullMarked
+package org.springframework.security.messaging.handler.invocation.reactive;
+
+import org.jspecify.annotations.NullMarked;

+ 1 - 1
messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java

@@ -107,7 +107,7 @@ public final class PathPatternMessageMatcher implements MessageMatcher<Object> {
 		return (pathMatchInfo != null) ? MatchResult.match(pathMatchInfo.getUriVariables()) : MatchResult.notMatch();
 	}
 
-	private static String getDestination(Message<?> message) {
+	private static @Nullable String getDestination(Message<?> message) {
 		return SimpMessageHeaderAccessor.getDestination(message.getHeaders());
 	}
 

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/util/matcher/package-info.java

@@ -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 matching messages.
+ */
+@NullMarked
+package org.springframework.security.messaging.util.matcher;
+
+import org.jspecify.annotations.NullMarked;

+ 3 - 1
messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfChannelInterceptor.java

@@ -19,6 +19,8 @@ package org.springframework.security.messaging.web.csrf;
 import java.security.MessageDigest;
 import java.util.Map;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
@@ -70,7 +72,7 @@ public final class XorCsrfChannelInterceptor implements ChannelInterceptor {
 	 * @param actual
 	 * @return
 	 */
-	private static boolean equalsConstantTime(String expected, String actual) {
+	private static boolean equalsConstantTime(String expected, @Nullable String actual) {
 		if (expected == actual) {
 			return true;
 		}

+ 3 - 1
messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfTokenUtils.java

@@ -18,6 +18,8 @@ package org.springframework.security.messaging.web.csrf;
 
 import java.util.Base64;
 
+import org.jspecify.annotations.Nullable;
+
 import org.springframework.security.crypto.codec.Utf8;
 import org.springframework.util.Assert;
 
@@ -33,7 +35,7 @@ final class XorCsrfTokenUtils {
 	private XorCsrfTokenUtils() {
 	}
 
-	static String getTokenValue(String actualToken, String token) {
+	static @Nullable String getTokenValue(@Nullable String actualToken, String token) {
 		byte[] actualBytes;
 		try {
 			actualBytes = Base64.getUrlDecoder().decode(actualToken);

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/web/csrf/package-info.java

@@ -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 CSRF protection in messages.
+ */
+@NullMarked
+package org.springframework.security.messaging.web.csrf;
+
+import org.jspecify.annotations.NullMarked;

+ 2 - 1
messaging/src/main/java/org/springframework/security/messaging/web/socket/server/CsrfTokenHandshakeInterceptor.java

@@ -19,6 +19,7 @@ package org.springframework.security.messaging.web.socket.server;
 import java.util.Map;
 
 import jakarta.servlet.http.HttpServletRequest;
+import org.jspecify.annotations.Nullable;
 
 import org.springframework.http.server.ServerHttpRequest;
 import org.springframework.http.server.ServerHttpResponse;
@@ -62,7 +63,7 @@ public final class CsrfTokenHandshakeInterceptor implements HandshakeInterceptor
 
 	@Override
 	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
-			Exception exception) {
+			@Nullable Exception exception) {
 	}
 
 }

+ 23 - 0
messaging/src/main/java/org/springframework/security/messaging/web/socket/server/package-info.java

@@ -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 Security CSRF protection.
+ */
+@NullMarked
+package org.springframework.security.messaging.web.socket.server;
+
+import org.jspecify.annotations.NullMarked;

+ 1 - 1
messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java

@@ -74,7 +74,7 @@ public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests {
 	@Test
 	public void createExpressionMessageMetadataSourceNoMatch() {
 		Collection<ConfigAttribute> attrs = this.source.getAttributes(this.message);
-		assertThat(attrs).isNull();
+		assertThat(attrs).isEmpty();
 	}
 
 	@Test

+ 2 - 2
messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java

@@ -67,8 +67,8 @@ public class DefaultMessageSecurityMetadataSourceTests {
 	}
 
 	@Test
-	public void getAttributesNull() {
-		assertThat(this.source.getAttributes(this.message)).isNull();
+	public void getAttributesEmpty() {
+		assertThat(this.source.getAttributes(this.message)).isEmpty();
 	}
 
 	@Test