From be64c67af5b4ad732de1b731ece6ce4fe3286932 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:24:08 -0500 Subject: [PATCH] Enable Null checking in spring-security-web via JSpecify Closes gh-16882 --- messaging/spring-security-messaging.gradle | 4 ++++ ...faultMessageSecurityExpressionHandler.java | 9 +++++-- ...ationContextSecurityExpressionHandler.java | 2 +- .../MessageExpressionConfigAttribute.java | 4 +++- .../expression/MessageExpressionVoter.java | 4 +++- .../access/expression/package-info.java | 23 ++++++++++++++++++ .../AuthorizationChannelInterceptor.java | 3 ++- .../intercept/ChannelSecurityInterceptor.java | 6 +++-- .../DefaultMessageSecurityMetadataSource.java | 3 ++- ...MatcherDelegatingAuthorizationManager.java | 7 +++--- .../access/intercept/package-info.java | 23 ++++++++++++++++++ ...thenticationPrincipalArgumentResolver.java | 7 ++++-- .../SecurityContextChannelInterceptor.java | 9 ++++--- ...yContextPropagationChannelInterceptor.java | 5 +++- .../messaging/context/package-info.java | 24 +++++++++++++++++++ ...thenticationPrincipalArgumentResolver.java | 11 +++++---- ...urrentSecurityContextArgumentResolver.java | 18 ++++++++------ .../invocation/reactive/package-info.java | 23 ++++++++++++++++++ .../matcher/PathPatternMessageMatcher.java | 2 +- .../messaging/util/matcher/package-info.java | 23 ++++++++++++++++++ .../web/csrf/XorCsrfChannelInterceptor.java | 4 +++- .../messaging/web/csrf/XorCsrfTokenUtils.java | 4 +++- .../messaging/web/csrf/package-info.java | 23 ++++++++++++++++++ .../server/CsrfTokenHandshakeInterceptor.java | 3 ++- .../web/socket/server/package-info.java | 23 ++++++++++++++++++ ...ageSecurityMetadataSourceFactoryTests.java | 2 +- ...ultMessageSecurityMetadataSourceTests.java | 4 ++-- 27 files changed, 237 insertions(+), 36 deletions(-) create mode 100644 messaging/src/main/java/org/springframework/security/messaging/access/expression/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/access/intercept/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/context/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/util/matcher/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/web/csrf/package-info.java create mode 100644 messaging/src/main/java/org/springframework/security/messaging/web/socket/server/package-info.java diff --git a/messaging/spring-security-messaging.gradle b/messaging/spring-security-messaging.gradle index 8b63a7903f..c231057267 100644 --- a/messaging/spring-security-messaging.gradle +++ b/messaging/spring-security-messaging.gradle @@ -1,3 +1,7 @@ +plugins { + id 'security-nullability' +} + apply plugin: 'io.spring.convention.spring-module' dependencies { diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java index ca62546e53..4346d3fb4a 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandler.java +++ b/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 extends AbstractSecurity Message 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 invocation) { return createSecurityExpressionRoot(() -> authentication, invocation); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageAuthorizationContextSecurityExpressionHandler.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageAuthorizationContextSecurityExpressionHandler.java index e3fd63584c..c8504bd00c 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageAuthorizationContextSecurityExpressionHandler.java +++ b/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); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java index aa686b4698..61ae0481f3 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java +++ b/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; } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java index 7da2e9891c..c4866cab3c 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java +++ b/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 implements AccessDecisionVoter return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED; } - private MessageExpressionConfigAttribute findConfigAttribute(Collection attributes) { + private @Nullable MessageExpressionConfigAttribute findConfigAttribute(Collection attributes) { for (ConfigAttribute attribute : attributes) { if (attribute instanceof MessageExpressionConfigAttribute) { return (MessageExpressionConfigAttribute) attribute; diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/expression/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/access/expression/package-info.java new file mode 100644 index 0000000000..075b0282d3 --- /dev/null +++ b/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; diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java index 10226d8e65..381ac65ad1 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java +++ b/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 void publishAuthorizationEvent(Supplier authentication, T object, - AuthorizationResult result) { + @Nullable AuthorizationResult result) { } diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java index 87d221bc2d..d4863b6919 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java +++ b/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() { diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java index ffb5ab5a59..7ab7315e63 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java +++ b/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 diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java index 5b0cd59703..f0d06dbd69 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.java +++ b/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 authentication, + public @Nullable AuthorizationResult authorize(Supplier 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> matchers = new ArrayList<>(patterns.length); for (String pattern : patterns) { MessageMatcher matcher = this.messageMatcherBuilder.matcher(type, pattern); diff --git a/messaging/src/main/java/org/springframework/security/messaging/access/intercept/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/access/intercept/package-info.java new file mode 100644 index 0000000000..888bb545b5 --- /dev/null +++ b/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; diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java index 83b1c395bc..b270fdaa71 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java +++ b/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()); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java index ed2fdbe9fe..178e608668 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextChannelInterceptor.java +++ b/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; } diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextPropagationChannelInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextPropagationChannelInterceptor.java index debb2176ef..9321c49a78 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/context/SecurityContextPropagationChannelInterceptor.java +++ b/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(); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/context/package-info.java new file mode 100644 index 0000000000..e990fa2a0d --- /dev/null +++ b/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; 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 4609fa74e3..634c13c97c 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,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()); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java index 1cc068278b..08044827ea 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java +++ b/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()); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/package-info.java new file mode 100644 index 0000000000..49f5520bb4 --- /dev/null +++ b/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; diff --git a/messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java index c225cf4396..b99bfad5f8 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java +++ b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/PathPatternMessageMatcher.java @@ -107,7 +107,7 @@ public final class PathPatternMessageMatcher implements MessageMatcher { 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()); } diff --git a/messaging/src/main/java/org/springframework/security/messaging/util/matcher/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/util/matcher/package-info.java new file mode 100644 index 0000000000..305d911432 --- /dev/null +++ b/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; diff --git a/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfChannelInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfChannelInterceptor.java index fcfee7050c..fccb6bc084 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfChannelInterceptor.java +++ b/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; } diff --git a/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfTokenUtils.java b/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfTokenUtils.java index 3498b03572..c2d0a5393d 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/web/csrf/XorCsrfTokenUtils.java +++ b/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); diff --git a/messaging/src/main/java/org/springframework/security/messaging/web/csrf/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/web/csrf/package-info.java new file mode 100644 index 0000000000..30a4182a67 --- /dev/null +++ b/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; diff --git a/messaging/src/main/java/org/springframework/security/messaging/web/socket/server/CsrfTokenHandshakeInterceptor.java b/messaging/src/main/java/org/springframework/security/messaging/web/socket/server/CsrfTokenHandshakeInterceptor.java index b99adcc73b..de00adc2fd 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/web/socket/server/CsrfTokenHandshakeInterceptor.java +++ b/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) { } } diff --git a/messaging/src/main/java/org/springframework/security/messaging/web/socket/server/package-info.java b/messaging/src/main/java/org/springframework/security/messaging/web/socket/server/package-info.java new file mode 100644 index 0000000000..255865bf01 --- /dev/null +++ b/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; diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java index 26fd63fa7e..acb8695cf3 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java +++ b/messaging/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java @@ -74,7 +74,7 @@ public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests { @Test public void createExpressionMessageMetadataSourceNoMatch() { Collection attrs = this.source.getAttributes(this.message); - assertThat(attrs).isNull(); + assertThat(attrs).isEmpty(); } @Test diff --git a/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java b/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java index d736116ee6..686ee46515 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java +++ b/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