From c2ba662b914aa308d689138e27de63c3fa390410 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:02:17 -0500 Subject: [PATCH] Enable Null checking in spring-security-web via JSpecify Closes gh-17535 --- .../annotation/web/HttpSecurityDslTests.kt | 18 ++--- .../config/annotation/web/LogoutDslTests.kt | 4 +- .../annotation/web/RequiresChannelDslTests.kt | 4 +- .../annotation/web/SecurityContextDslTests.kt | 2 +- .../web/server/ServerHttpBasicDslTests.kt | 4 +- .../security/access/ConfigAttribute.java | 3 + .../expression/SecurityExpressionRoot.java | 2 +- ...efaultMethodSecurityExpressionHandler.java | 1 + .../security/access/vote/RoleVoter.java | 3 + .../AuthenticationTrustResolver.java | 2 + .../UsernamePasswordAuthenticationToken.java | 9 ++- .../AbstractJaasAuthenticationProvider.java | 6 +- .../ott/OneTimeTokenAuthenticationToken.java | 4 +- .../AuthorizationEventPublisher.java | 5 +- .../SpringAuthorizationEventPublisher.java | 4 +- .../MethodExpressionAuthorizationManager.java | 1 + .../NoOpAuthorizationEventPublisher.java | 4 +- .../core/AuthenticationException.java | 2 +- web/spring-security-web.gradle | 4 + .../web/DefaultSecurityFilterChain.java | 5 +- .../security/web/FilterChainProxy.java | 7 +- .../security/web/FilterInvocation.java | 61 +++++++------- .../web/ObservationFilterChainDecorator.java | 7 +- .../security/web/PortMapper.java | 6 +- .../security/web/PortMapperImpl.java | 6 +- .../security/web/PortResolverImpl.java | 3 +- .../web/access/AccessDeniedHandlerImpl.java | 3 +- ...anagerWebInvocationPrivilegeEvaluator.java | 6 +- ...efaultWebInvocationPrivilegeEvaluator.java | 6 +- .../access/ExceptionTranslationFilter.java | 3 +- .../access/PathPatternRequestTransformer.java | 3 +- ...gatingWebInvocationPrivilegeEvaluator.java | 6 +- .../channel/AbstractRetryEntryPoint.java | 3 +- .../channel/ChannelDecisionManagerImpl.java | 5 +- .../channel/ChannelProcessingFilter.java | 9 ++- .../channel/InsecureChannelProcessor.java | 6 +- .../channel/RetryWithHttpEntryPoint.java | 4 +- .../channel/RetryWithHttpsEntryPoint.java | 4 +- .../channel/SecureChannelProcessor.java | 5 +- .../web/access/channel/package-info.java | 3 + ...ariableEvaluationContextPostProcessor.java | 5 +- .../DefaultHttpSecurityExpressionHandler.java | 7 +- .../DefaultWebSecurityExpressionHandler.java | 4 +- .../DelegatingEvaluationContext.java | 8 +- .../WebExpressionConfigAttribute.java | 3 + .../access/expression/WebExpressionVoter.java | 3 +- .../expression/WebSecurityExpressionRoot.java | 7 +- .../web/access/expression/package-info.java | 3 + .../access/intercept/AuthorizationFilter.java | 3 +- ...ilterInvocationSecurityMetadataSource.java | 3 +- .../intercept/FilterSecurityInterceptor.java | 7 +- .../web/access/intercept/RequestKey.java | 8 +- ...MatcherDelegatingAuthorizationManager.java | 2 +- .../web/access/intercept/package-info.java | 3 + .../security/web/access/package-info.java | 3 + .../aot/hint/WebMvcSecurityRuntimeHints.java | 4 +- .../security/web/aot/hint/package-info.java | 23 ++++++ ...bstractAuthenticationProcessingFilter.java | 10 ++- ...AuthenticationTargetUrlRequestHandler.java | 15 ++-- .../AuthenticationConverter.java | 3 +- .../authentication/AuthenticationFilter.java | 3 +- .../DelegatingAuthenticationConverter.java | 3 +- .../DelegatingAuthenticationEntryPoint.java | 1 + .../LoginUrlAuthenticationEntryPoint.java | 4 +- .../NullRememberMeServices.java | 3 +- .../authentication/RememberMeServices.java | 3 +- ...SimpleUrlAuthenticationFailureHandler.java | 3 +- .../WebAuthenticationDetails.java | 9 ++- .../logout/CompositeLogoutHandler.java | 4 +- .../logout/CookieClearingLogoutHandler.java | 4 +- .../DelegatingLogoutSuccessHandler.java | 7 +- .../logout/ForwardLogoutSuccessHandler.java | 5 +- .../logout/HeaderWriterLogoutHandler.java | 4 +- ...tpStatusReturningLogoutSuccessHandler.java | 5 +- .../authentication/logout/LogoutFilter.java | 2 + .../authentication/logout/LogoutHandler.java | 3 +- ...utSuccessEventPublishingLogoutHandler.java | 6 +- .../logout/LogoutSuccessHandler.java | 5 +- .../logout/SecurityContextLogoutHandler.java | 4 +- .../logout/SimpleUrlLogoutSuccessHandler.java | 5 +- .../authentication/logout/package-info.java | 3 + ...ltGenerateOneTimeTokenRequestResolver.java | 3 +- .../ott/GenerateOneTimeTokenFilter.java | 2 +- .../OneTimeTokenAuthenticationConverter.java | 3 +- .../web/authentication/ott/package-info.java | 23 ++++++ .../web/authentication/package-info.java | 3 + .../HaveIBeenPwnedRestApiPasswordChecker.java | 3 +- .../authentication/password/package-info.java | 23 ++++++ ...tractPreAuthenticatedProcessingFilter.java | 14 ++-- ...reAuthenticatedAuthenticationProvider.java | 4 +- .../PreAuthenticatedAuthenticationToken.java | 10 ++- .../RequestAttributeAuthenticationFilter.java | 3 +- .../RequestHeaderAuthenticationFilter.java | 3 +- ...ticatedWebAuthenticationDetailsSource.java | 1 + .../J2eePreAuthenticatedProcessingFilter.java | 3 +- .../WebXmlMappableAttributesRetriever.java | 6 +- .../preauth/j2ee/package-info.java | 3 + .../authentication/preauth/package-info.java | 3 + .../DefaultWASUsernameAndGroupsExtractor.java | 21 ++--- .../WASUsernameAndGroupsExtractor.java | 4 +- ...pherePreAuthenticatedProcessingFilter.java | 3 +- .../preauth/websphere/package-info.java | 3 + .../x509/SubjectDnX509PrincipalExtractor.java | 1 + .../x509/X509AuthenticationFilter.java | 7 +- .../preauth/x509/package-info.java | 3 + .../AbstractRememberMeServices.java | 12 +-- .../InMemoryTokenRepositoryImpl.java | 9 ++- .../rememberme/JdbcTokenRepositoryImpl.java | 23 ++++-- ...ersistentTokenBasedRememberMeServices.java | 4 +- .../rememberme/PersistentTokenRepository.java | 4 +- .../RememberMeAuthenticationFilter.java | 21 ++--- .../TokenBasedRememberMeServices.java | 24 +++--- .../rememberme/package-info.java | 3 + ...tSessionControlAuthenticationStrategy.java | 4 +- .../NullAuthenticatedSessionStrategy.java | 3 +- ...RegisterSessionAuthenticationStrategy.java | 4 +- .../authentication/session/package-info.java | 3 + .../AuthenticationSwitchUserEvent.java | 8 +- .../SwitchUserAuthorityChanger.java | 5 +- .../switchuser/SwitchUserFilter.java | 22 +++-- .../SwitchUserGrantedAuthority.java | 4 +- .../switchuser/package-info.java | 3 + .../ui/DefaultLoginPageGeneratingFilter.java | 25 +++--- .../web/authentication/ui/HtmlTemplates.java | 8 +- .../web/authentication/ui/package-info.java | 3 + .../www/BasicAuthenticationConverter.java | 3 +- .../www/BasicAuthenticationEntryPoint.java | 5 +- .../www/BasicAuthenticationFilter.java | 7 +- .../authentication/www/DigestAuthUtils.java | 17 ++-- .../www/DigestAuthenticationEntryPoint.java | 9 ++- .../www/DigestAuthenticationFilter.java | 51 +++++++----- .../web/authentication/www/package-info.java | 3 + .../web/bind/annotation/package-info.java | 23 ++++++ .../security/web/bind/package-info.java | 23 ++++++ ...thenticationPrincipalArgumentResolver.java | 13 +-- .../web/bind/support/package-info.java | 23 ++++++ ...ractSecurityWebApplicationInitializer.java | 7 +- .../DelegatingSecurityContextRepository.java | 6 +- .../HttpSessionSecurityContextRepository.java | 9 ++- .../NullSecurityContextRepository.java | 4 +- ...estAttributeSecurityContextRepository.java | 4 +- .../context/SecurityContextRepository.java | 6 +- .../SupplierDeferredSecurityContext.java | 5 +- .../security/web/context/package-info.java | 3 + ...yContextCallableProcessingInterceptor.java | 10 ++- .../context/request/async/package-info.java | 23 ++++++ .../web/context/support/package-info.java | 23 ++++++ .../web/csrf/CookieCsrfTokenRepository.java | 13 +-- .../web/csrf/CsrfAuthenticationStrategy.java | 3 +- .../security/web/csrf/CsrfFilter.java | 3 +- .../security/web/csrf/CsrfLogoutHandler.java | 4 +- .../web/csrf/CsrfTokenRepository.java | 5 +- .../web/csrf/CsrfTokenRequestHandler.java | 3 +- .../web/csrf/CsrfTokenRequestResolver.java | 3 +- .../csrf/HttpSessionCsrfTokenRepository.java | 5 +- .../web/csrf/InvalidCsrfTokenException.java | 3 +- .../web/csrf/MissingCsrfTokenException.java | 4 +- .../web/csrf/RepositoryDeferredCsrfToken.java | 7 +- .../XorCsrfTokenRequestAttributeHandler.java | 7 +- .../security/web/csrf/package-info.java | 23 ++++++ .../security/web/debug/DebugFilter.java | 5 +- .../security/web/debug/package-info.java | 23 ++++++ .../security/web/firewall/RequestWrapper.java | 11 +-- .../security/web/firewall/package-info.java | 23 ++++++ .../security/web/header/package-info.java | 23 ++++++ ...CrossOriginEmbedderPolicyHeaderWriter.java | 5 +- .../CrossOriginOpenerPolicyHeaderWriter.java | 5 +- ...CrossOriginResourcePolicyHeaderWriter.java | 5 +- .../PermissionsPolicyHeaderWriter.java | 3 +- .../writers/ReferrerPolicyHeaderWriter.java | 6 +- .../writers/XXssProtectionHeaderWriter.java | 3 +- .../XFrameOptionsHeaderWriter.java | 5 +- .../writers/frameoptions/package-info.java | 23 ++++++ .../web/header/writers/package-info.java | 23 ++++++ .../security/web/http/package-info.java | 23 ++++++ .../web/jaasapi/JaasApiIntegrationFilter.java | 3 +- .../security/web/jaasapi/package-info.java | 3 + .../jackson2/DefaultSavedRequestMixin.java | 3 +- .../security/web/jackson2/package-info.java | 3 + ...thenticationPrincipalArgumentResolver.java | 26 +++--- .../annotation/CsrfTokenArgumentResolver.java | 6 +- ...urrentSecurityContextArgumentResolver.java | 19 +++-- .../web/method/annotation/package-info.java | 23 ++++++ .../security/web/package-info.java | 3 + ...thenticationPrincipalArgumentResolver.java | 14 +++- ...urrentSecurityContextArgumentResolver.java | 16 ++-- .../method/annotation/package-info.java | 23 ++++++ .../reactive/result/view/package-info.java | 23 ++++++ .../web/savedrequest/CookieRequestCache.java | 15 ++-- .../web/savedrequest/DefaultSavedRequest.java | 81 ++++++++++--------- .../security/web/savedrequest/Enumerator.java | 1 + .../web/savedrequest/FastHttpDateFormat.java | 10 ++- .../savedrequest/HttpSessionRequestCache.java | 5 +- .../web/savedrequest/NullRequestCache.java | 5 +- .../web/savedrequest/RequestCache.java | 5 +- .../web/savedrequest/SavedRequest.java | 3 +- .../SavedRequestAwareWrapper.java | 9 ++- .../web/savedrequest/SimpleSavedRequest.java | 2 + .../web/savedrequest/package-info.java | 3 + .../ObservationWebFilterChainDecorator.java | 32 ++++---- ...rolServerAuthenticationSuccessHandler.java | 3 +- ...ionServerAuthenticationSuccessHandler.java | 6 +- .../authentication/SwitchUserWebFilter.java | 6 +- .../authentication/logout/package-info.java | 23 ++++++ ...erOneTimeTokenAuthenticationConverter.java | 3 +- .../authentication/ott/package-info.java | 23 ++++++ .../server/authentication/package-info.java | 23 ++++++ .../ExceptionTranslationWebFilter.java | 3 +- .../server/authorization/package-info.java | 23 ++++++ .../NoOpServerSecurityContextRepository.java | 3 +- .../ServerSecurityContextRepository.java | 3 +- ...essionServerSecurityContextRepository.java | 3 +- .../web/server/context/package-info.java | 23 ++++++ .../csrf/CookieServerCsrfTokenRepository.java | 9 ++- .../csrf/ServerCsrfTokenRepository.java | 3 +- .../WebSessionServerCsrfTokenRepository.java | 5 +- ...erverCsrfTokenRequestAttributeHandler.java | 3 +- .../web/server/csrf/package-info.java | 23 ++++++ .../StrictServerWebExchangeFirewall.java | 7 +- .../web/server/firewall/package-info.java | 23 ++++++ ...SecurityPolicyServerHttpHeadersWriter.java | 7 +- ...EmbedderPolicyServerHttpHeadersWriter.java | 3 +- ...inOpenerPolicyServerHttpHeadersWriter.java | 3 +- ...ResourcePolicyServerHttpHeadersWriter.java | 3 +- .../FeaturePolicyServerHttpHeadersWriter.java | 3 +- ...missionsPolicyServerHttpHeadersWriter.java | 3 +- .../web/server/header/package-info.java | 23 ++++++ .../web/server/jackson2/package-info.java | 23 ++++++ .../security/web/server/package-info.java | 23 ++++++ .../CookieServerRequestCache.java | 6 +- .../WebSessionServerRequestCache.java | 3 +- .../web/server/savedrequest/package-info.java | 24 ++++++ .../web/server/transport/package-info.java | 23 ++++++ .../ui/LoginPageGeneratingWebFilter.java | 3 +- .../security/web/server/ui/package-info.java | 23 ++++++ ...PatternParserServerWebExchangeMatcher.java | 14 ++-- .../matcher/ServerWebExchangeMatchers.java | 5 +- .../web/server/util/matcher/package-info.java | 24 ++++++ .../csrf/CsrfRequestDataValueProcessor.java | 3 +- .../servlet/support/csrf/package-info.java | 24 ++++++ .../servlet/util/matcher/package-info.java | 23 ++++++ .../HttpServlet3RequestFactory.java | 15 ++-- ...curityContextHolderAwareRequestFilter.java | 8 +- ...urityContextHolderAwareRequestWrapper.java | 7 +- .../security/web/servletapi/package-info.java | 3 + .../web/session/ConcurrentSessionFilter.java | 8 +- .../SessionInformationExpiredEvent.java | 7 +- .../web/session/SessionManagementFilter.java | 3 +- .../security/web/session/package-info.java | 3 + .../security/web/transport/package-info.java | 23 ++++++ .../security/web/util/RedirectUrlBuilder.java | 14 ++-- .../security/web/util/ThrowableAnalyzer.java | 7 +- .../web/util/ThrowableCauseExtractor.java | 4 +- .../security/web/util/UrlUtils.java | 3 +- .../web/util/matcher/ELRequestMatcher.java | 6 +- .../web/util/matcher/RegexRequestMatcher.java | 7 +- .../matcher/RequestHeaderRequestMatcher.java | 5 +- .../web/util/matcher/package-info.java | 24 ++++++ .../security/web/util/package-info.java | 3 + ...InvocationSecurityMetadataSourceTests.java | 2 +- ...hMatcherServerWebExchangeMatcherTests.java | 4 +- 261 files changed, 1782 insertions(+), 537 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/web/aot/hint/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/authentication/ott/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/authentication/password/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/bind/annotation/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/bind/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/bind/support/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/context/request/async/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/context/support/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/csrf/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/debug/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/firewall/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/header/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/frameoptions/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/header/writers/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/http/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/method/annotation/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/reactive/result/view/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/logout/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/ott/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/authentication/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/authorization/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/context/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/csrf/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/firewall/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/header/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/jackson2/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/savedrequest/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/transport/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/ui/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/server/util/matcher/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/servlet/support/csrf/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/servlet/util/matcher/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/transport/package-info.java create mode 100644 web/src/main/java/org/springframework/security/web/util/matcher/package-info.java diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt index 4abd82e745..5cfaadee43 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDslTests.kt @@ -367,7 +367,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filters: List = filterChain.getFilters("/") + val filters: List? = filterChain.getFilters("/") assertThat(filters).anyMatch { it is CustomFilter } } @@ -390,7 +390,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterConfigReified::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filters: List = filterChain.getFilters("/") + val filters: List? = filterChain.getFilters("/") assertThat(filters).anyMatch { it is CustomFilter } } @@ -413,7 +413,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterAfterConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filters: List> = filterChain.getFilters("/").map { it.javaClass } + val filters: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filters).containsSubsequence( UsernamePasswordAuthenticationFilter::class.java, @@ -440,7 +440,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterAfterConfigReified::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass } + val filterClasses: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filterClasses).containsSubsequence( UsernamePasswordAuthenticationFilter::class.java, @@ -467,7 +467,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterBeforeConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filters: List> = filterChain.getFilters("/").map { it.javaClass } + val filters: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filters).containsSubsequence( CustomFilter::class.java, @@ -494,7 +494,7 @@ class HttpSecurityDslTests { this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass } + val filterClasses: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filterClasses).containsSubsequence( CustomFilter::class.java, @@ -523,7 +523,7 @@ class HttpSecurityDslTests { this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass } + val filterClasses: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filterClasses).contains( CustomFilter::class.java @@ -535,7 +535,7 @@ class HttpSecurityDslTests { this.spring.register(CustomSecurityConfigurerConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass } + val filterClasses: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filterClasses).contains( CustomFilter::class.java @@ -588,7 +588,7 @@ class HttpSecurityDslTests { this.spring.register(CustomDslUsingWithConfig::class.java).autowire() val filterChain = spring.context.getBean(FilterChainProxy::class.java) - val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass } + val filterClasses: List> = filterChain.getFilters("/")!!.map { it.javaClass } assertThat(filterClasses).contains( UsernamePasswordAuthenticationFilter::class.java diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/LogoutDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/LogoutDslTests.kt index 37f71d0c42..e880d6f5e1 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/LogoutDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/LogoutDslTests.kt @@ -350,8 +350,8 @@ class LogoutDslTests { class NoopLogoutHandler: LogoutHandler { override fun logout( - request: HttpServletRequest?, - response: HttpServletResponse?, + request: HttpServletRequest, + response: HttpServletResponse, authentication: Authentication? ) { } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RequiresChannelDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RequiresChannelDslTests.kt index d82e1d1ff4..876d92e4f1 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/RequiresChannelDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/RequiresChannelDslTests.kt @@ -132,8 +132,8 @@ class RequiresChannelDslTests { companion object { val CHANNEL_PROCESSOR: ChannelProcessor = object : ChannelProcessor { - override fun decide(invocation: FilterInvocation?, config: MutableCollection?) {} - override fun supports(attribute: ConfigAttribute?): Boolean = true + override fun decide(invocation: FilterInvocation, config: MutableCollection) {} + override fun supports(attribute: ConfigAttribute): Boolean = true } } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/SecurityContextDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/SecurityContextDslTests.kt index 12aec436be..96ff4aa2c4 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/SecurityContextDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/SecurityContextDslTests.kt @@ -93,7 +93,7 @@ class SecurityContextDslTests { testContext.autowire() val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java) // @formatter:off - val filterTypes = filterChainProxy.getFilters("/").toList() + val filterTypes = filterChainProxy.getFilters("/")!!.toList() assertThat(filterTypes) .anyMatch { it is SecurityContextHolderFilter } diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt index ee23dbb338..b80aca8792 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt @@ -270,8 +270,8 @@ class ServerHttpBasicDslTests { open class MockServerAuthenticationFailureHandler: ServerAuthenticationFailureHandler { override fun onAuthenticationFailure( - webFilterExchange: WebFilterExchange?, - exception: AuthenticationException? + webFilterExchange: WebFilterExchange, + exception: AuthenticationException ): Mono { return Mono.empty() } diff --git a/core/src/main/java/org/springframework/security/access/ConfigAttribute.java b/core/src/main/java/org/springframework/security/access/ConfigAttribute.java index d413cca993..1f8ef11ce8 100644 --- a/core/src/main/java/org/springframework/security/access/ConfigAttribute.java +++ b/core/src/main/java/org/springframework/security/access/ConfigAttribute.java @@ -18,6 +18,8 @@ package org.springframework.security.access; import java.io.Serializable; +import org.jspecify.annotations.NullUnmarked; + import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; @@ -45,6 +47,7 @@ import org.springframework.security.core.annotation.SecurityAnnotationScanner; * {@link AuthorizationManager}. */ @Deprecated +@NullUnmarked public interface ConfigAttribute extends Serializable { /** diff --git a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java index 7021d98d3f..d2930dec6a 100644 --- a/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java +++ b/core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java @@ -177,7 +177,7 @@ public abstract class SecurityExpressionRoot implements SecurityExpressionOperat this.trustResolver = trustResolver; } - public void setRoleHierarchy(RoleHierarchy roleHierarchy) { + public void setRoleHierarchy(@Nullable RoleHierarchy roleHierarchy) { this.roleHierarchy = roleHierarchy; } diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java index aed139cc81..e186d2ad05 100644 --- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java +++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java @@ -85,6 +85,7 @@ public class DefaultMethodSecurityExpressionHandler extends AbstractSecurityExpr } @Override + @SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation public EvaluationContext createEvaluationContext(Supplier authentication, MethodInvocation mi) { MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi); diff --git a/core/src/main/java/org/springframework/security/access/vote/RoleVoter.java b/core/src/main/java/org/springframework/security/access/vote/RoleVoter.java index a6c6dce89f..86a6ffe828 100644 --- a/core/src/main/java/org/springframework/security/access/vote/RoleVoter.java +++ b/core/src/main/java/org/springframework/security/access/vote/RoleVoter.java @@ -18,6 +18,8 @@ package org.springframework.security.access.vote; import java.util.Collection; +import org.jspecify.annotations.NullUnmarked; + import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; @@ -53,6 +55,7 @@ import org.springframework.security.core.GrantedAuthority; * instead */ @Deprecated +@NullUnmarked public class RoleVoter implements AccessDecisionVoter { private String rolePrefix = "ROLE_"; diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolver.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolver.java index ceddacebb4..fba8f77cb2 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolver.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolver.java @@ -18,6 +18,7 @@ package org.springframework.security.authentication; import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; import org.springframework.security.core.Authentication; /** @@ -80,6 +81,7 @@ public interface AuthenticationTrustResolver { * {@link Authentication#isAuthenticated()} is true. * @since 6.1.7 */ + @Contract("null -> false") default boolean isAuthenticated(@Nullable Authentication authentication) { return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication); } diff --git a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java index 2ec7d269bf..c25b4a9ce0 100644 --- a/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java @@ -39,7 +39,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT private static final long serialVersionUID = 620L; - private final Object principal; + private final @Nullable Object principal; private @Nullable Object credentials; @@ -49,7 +49,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT * will return false. * */ - public UsernamePasswordAuthenticationToken(Object principal, @Nullable Object credentials) { + public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) { super(null); this.principal = principal; this.credentials = credentials; @@ -82,7 +82,8 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT * * @since 5.7 */ - public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, @Nullable Object credentials) { + public static UsernamePasswordAuthenticationToken unauthenticated(@Nullable Object principal, + @Nullable Object credentials) { return new UsernamePasswordAuthenticationToken(principal, credentials); } @@ -106,7 +107,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT } @Override - public Object getPrincipal() { + public @Nullable Object getPrincipal() { return this.principal; } diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java index aae989b290..5988d2d1f5 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java @@ -178,8 +178,10 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati // applied. authorities = getAuthorities(principals); // Convert the authorities set back to an array and apply it to the token. - JaasAuthenticationToken result = new JaasAuthenticationToken(request.getPrincipal(), - request.getCredentials(), new ArrayList<>(authorities), loginContext); + Object principal = request.getPrincipal(); + Assert.notNull(principal, "The principal cannot be null"); + JaasAuthenticationToken result = new JaasAuthenticationToken(principal, request.getCredentials(), + new ArrayList<>(authorities), loginContext); // Publish the success event publishSuccessEvent(result); // we're done, return the token. diff --git a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationToken.java index 1f91779e3c..dd3cb8b2ec 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationToken.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationToken.java @@ -71,8 +71,8 @@ public class OneTimeTokenAuthenticationToken extends AbstractAuthenticationToken * @deprecated Please use constructor that takes a {@link String} instead */ @Deprecated(forRemoval = true, since = "7.0") - public static OneTimeTokenAuthenticationToken unauthenticated(String tokenValue) { - return new OneTimeTokenAuthenticationToken(null, tokenValue); + public static OneTimeTokenAuthenticationToken unauthenticated(@Nullable String tokenValue) { + return new OneTimeTokenAuthenticationToken(null, (tokenValue != null) ? tokenValue : ""); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java index ce78446479..5a9c2c705f 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java @@ -18,6 +18,8 @@ package org.springframework.security.authorization; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.security.authorization.event.AuthorizationGrantedEvent; import org.springframework.security.core.Authentication; @@ -46,6 +48,7 @@ public interface AuthorizationEventPublisher { * @param the secured object's type * @since 6.4 */ - void publishAuthorizationEvent(Supplier authentication, T object, AuthorizationResult result); + void publishAuthorizationEvent(Supplier authentication, T object, + @Nullable AuthorizationResult result); } diff --git a/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java index d5e54fc9c7..6ed7cd0b41 100644 --- a/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java +++ b/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java @@ -19,6 +19,8 @@ package org.springframework.security.authorization; import java.util.function.Predicate; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.security.authorization.event.AuthorizationGrantedEvent; @@ -57,7 +59,7 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve */ @Override public void publishAuthorizationEvent(Supplier authentication, T object, - AuthorizationResult result) { + @Nullable AuthorizationResult result) { if (result == null) { return; } diff --git a/core/src/main/java/org/springframework/security/authorization/method/MethodExpressionAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/MethodExpressionAuthorizationManager.java index 008b674d1c..3dd91f46bd 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/MethodExpressionAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/MethodExpressionAuthorizationManager.java @@ -74,6 +74,7 @@ public final class MethodExpressionAuthorizationManager implements Authorization * expression */ @Override + @SuppressWarnings("NullAway") // FIXME: Dataflow analysis limitation public AuthorizationResult authorize(Supplier authentication, MethodInvocation context) { EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context); diff --git a/core/src/main/java/org/springframework/security/authorization/method/NoOpAuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/method/NoOpAuthorizationEventPublisher.java index 0fa2ac1f07..cd0daa751b 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/NoOpAuthorizationEventPublisher.java +++ b/core/src/main/java/org/springframework/security/authorization/method/NoOpAuthorizationEventPublisher.java @@ -18,6 +18,8 @@ package org.springframework.security.authorization.method; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; @@ -32,7 +34,7 @@ final class NoOpAuthorizationEventPublisher implements AuthorizationEventPublish @Override public void publishAuthorizationEvent(Supplier authentication, T object, - AuthorizationResult result) { + @Nullable AuthorizationResult result) { } diff --git a/core/src/main/java/org/springframework/security/core/AuthenticationException.java b/core/src/main/java/org/springframework/security/core/AuthenticationException.java index 0a4fbaff60..37fea816f1 100644 --- a/core/src/main/java/org/springframework/security/core/AuthenticationException.java +++ b/core/src/main/java/org/springframework/security/core/AuthenticationException.java @@ -76,7 +76,7 @@ public abstract class AuthenticationException extends RuntimeException { * authentication attempt * @since 6.5 */ - public void setAuthenticationRequest(Authentication authenticationRequest) { + public void setAuthenticationRequest(@Nullable Authentication authenticationRequest) { Assert.notNull(authenticationRequest, "authenticationRequest cannot be null"); this.authenticationRequest = authenticationRequest; } diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle index 4b025cb16c..87ce691e0c 100644 --- a/web/spring-security-web.gradle +++ b/web/spring-security-web.gradle @@ -1,3 +1,7 @@ +plugins { + id 'security-nullability' +} + apply plugin: 'io.spring.convention.spring-module' configurations { diff --git a/web/src/main/java/org/springframework/security/web/DefaultSecurityFilterChain.java b/web/src/main/java/org/springframework/security/web/DefaultSecurityFilterChain.java index 97c503ef53..f60456ac53 100644 --- a/web/src/main/java/org/springframework/security/web/DefaultSecurityFilterChain.java +++ b/web/src/main/java/org/springframework/security/web/DefaultSecurityFilterChain.java @@ -24,6 +24,7 @@ import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; @@ -51,9 +52,9 @@ public final class DefaultSecurityFilterChain implements SecurityFilterChain, Be private final List filters; - private String beanName; + private @Nullable String beanName; - private ConfigurableListableBeanFactory beanFactory; + private @Nullable ConfigurableListableBeanFactory beanFactory; public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) { this(requestMatcher, Arrays.asList(filters)); diff --git a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java index 46ce8d11b3..c48d6b972b 100644 --- a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java +++ b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java @@ -30,6 +30,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.security.core.context.SecurityContextHolder; @@ -164,6 +165,7 @@ public class FilterChainProxy extends GenericFilterBean { private FilterChainDecorator filterChainDecorator = new VirtualFilterChainDecorator(); public FilterChainProxy() { + this(Collections.emptyList()); } public FilterChainProxy(SecurityFilterChain chain) { @@ -171,6 +173,7 @@ public class FilterChainProxy extends GenericFilterBean { } public FilterChainProxy(List filterChains) { + Assert.notNull(filterChains, "filterChains cannot be null"); this.filterChains = filterChains; } @@ -239,7 +242,7 @@ public class FilterChainProxy extends GenericFilterBean { * @param request the request to match * @return an ordered array of Filters defining the filter chain */ - private List getFilters(HttpServletRequest request) { + private @Nullable List getFilters(HttpServletRequest request) { int count = 0; for (SecurityFilterChain chain : this.filterChains) { if (logger.isTraceEnabled()) { @@ -258,7 +261,7 @@ public class FilterChainProxy extends GenericFilterBean { * @param url the URL * @return matching filter list */ - public List getFilters(String url) { + public @Nullable List getFilters(String url) { PathPatternRequestTransformer requestTransformer = new PathPatternRequestTransformer(); HttpServletRequest transformed = requestTransformer.transform(new FilterInvocation(url, "GET").getRequest()); return getFilters(this.firewall.getFirewalledRequest(transformed)); diff --git a/web/src/main/java/org/springframework/security/web/FilterInvocation.java b/web/src/main/java/org/springframework/security/web/FilterInvocation.java index 25d4319746..851d349cdd 100644 --- a/web/src/main/java/org/springframework/security/web/FilterInvocation.java +++ b/web/src/main/java/org/springframework/security/web/FilterInvocation.java @@ -36,6 +36,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; import org.springframework.security.web.util.UrlUtils; @@ -62,11 +63,11 @@ public class FilterInvocation { throw new UnsupportedOperationException("Dummy filter chain"); }; - private FilterChain chain; + private final FilterChain chain; private HttpServletRequest request; - private HttpServletResponse response; + private @Nullable HttpServletResponse response; public FilterInvocation(ServletRequest request, ServletResponse response, FilterChain chain) { Assert.isTrue(request != null && response != null && chain != null, "Cannot pass null values to constructor"); @@ -79,11 +80,12 @@ public class FilterInvocation { this(null, servletPath, method); } - public FilterInvocation(String contextPath, String servletPath, String method) { + public FilterInvocation(@Nullable String contextPath, String servletPath, String method) { this(contextPath, servletPath, method, null); } - public FilterInvocation(String contextPath, String servletPath, String method, ServletContext servletContext) { + public FilterInvocation(@Nullable String contextPath, String servletPath, @Nullable String method, + @Nullable ServletContext servletContext) { this(contextPath, servletPath, null, null, method, servletContext); } @@ -91,8 +93,8 @@ public class FilterInvocation { this(contextPath, servletPath, pathInfo, query, method, null); } - public FilterInvocation(String contextPath, String servletPath, String pathInfo, String query, String method, - ServletContext servletContext) { + public FilterInvocation(@Nullable String contextPath, String servletPath, @Nullable String pathInfo, + @Nullable String query, @Nullable String method, @Nullable ServletContext servletContext) { DummyRequest request = new DummyRequest(); contextPath = (contextPath != null) ? contextPath : "/cp"; request.setContextPath(contextPath); @@ -103,6 +105,7 @@ public class FilterInvocation { request.setMethod(method); request.setServletContext(servletContext); this.request = request; + this.chain = DUMMY_CHAIN; } public FilterChain getChain() { @@ -124,7 +127,7 @@ public class FilterInvocation { return this.request; } - public HttpServletResponse getHttpResponse() { + public @Nullable HttpServletResponse getHttpResponse() { return this.response; } @@ -140,7 +143,7 @@ public class FilterInvocation { return getHttpRequest(); } - public HttpServletResponse getResponse() { + public @Nullable HttpServletResponse getResponse() { return getHttpResponse(); } @@ -160,19 +163,19 @@ public class FilterInvocation { DummyRequest.class.getClassLoader(), new Class[] { HttpServletRequest.class }, new UnsupportedOperationExceptionInvocationHandler()); - private String requestURI; + private @Nullable String requestURI; private String contextPath = ""; - private String servletPath; + private @Nullable String servletPath; - private String pathInfo; + private @Nullable String pathInfo; - private String queryString; + private @Nullable String queryString; - private String method; + private @Nullable String method; - private ServletContext servletContext; + private @Nullable ServletContext servletContext; private final HttpHeaders headers = new HttpHeaders(); @@ -188,7 +191,7 @@ public class FilterInvocation { } @Override - public Object getAttribute(String attributeName) { + public @Nullable Object getAttribute(String attributeName) { return null; } @@ -196,12 +199,12 @@ public class FilterInvocation { this.requestURI = requestURI; } - void setPathInfo(String pathInfo) { + void setPathInfo(@Nullable String pathInfo) { this.pathInfo = pathInfo; } @Override - public String getRequestURI() { + public @Nullable String getRequestURI() { return this.requestURI; } @@ -219,40 +222,40 @@ public class FilterInvocation { } @Override - public String getServletPath() { + public @Nullable String getServletPath() { return this.servletPath; } - void setMethod(String method) { + void setMethod(@Nullable String method) { this.method = method; } @Override - public String getMethod() { + public @Nullable String getMethod() { return this.method; } @Override - public String getPathInfo() { + public @Nullable String getPathInfo() { return this.pathInfo; } @Override - public String getQueryString() { + public @Nullable String getQueryString() { return this.queryString; } - void setQueryString(String queryString) { + void setQueryString(@Nullable String queryString) { this.queryString = queryString; } @Override - public String getServerName() { + public @Nullable String getServerName() { return null; } @Override - public String getHeader(String name) { + public @Nullable String getHeader(String name) { return this.headers.getFirst(name); } @@ -284,7 +287,7 @@ public class FilterInvocation { } @Override - public String getParameter(String name) { + public @Nullable String getParameter(String name) { String[] array = this.parameters.get(name); return (array != null && array.length > 0) ? array[0] : null; } @@ -300,7 +303,7 @@ public class FilterInvocation { } @Override - public String[] getParameterValues(String name) { + public String @Nullable [] getParameterValues(String name) { return this.parameters.get(name); } @@ -309,11 +312,11 @@ public class FilterInvocation { } @Override - public ServletContext getServletContext() { + public @Nullable ServletContext getServletContext() { return this.servletContext; } - void setServletContext(ServletContext servletContext) { + void setServletContext(@Nullable ServletContext servletContext) { this.servletContext = servletContext; } diff --git a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java index 1c0942fa7c..5cca192101 100644 --- a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java +++ b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java @@ -37,6 +37,8 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.util.StringUtils; @@ -49,6 +51,7 @@ import org.springframework.util.StringUtils; * @author Nikita Konev * @since 6.0 */ +@NullUnmarked // https://github.com/spring-projects/spring-security/issues/17815 public final class ObservationFilterChainDecorator implements FilterChainProxy.FilterChainDecorator { private static final Log logger = LogFactory.getLog(FilterChainProxy.class); @@ -507,7 +510,7 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F private final String filterSection; - private String filterName; + private @Nullable String filterName; private int chainPosition; @@ -530,7 +533,7 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F return this.filterSection; } - String getFilterName() { + @Nullable String getFilterName() { return this.filterName; } diff --git a/web/src/main/java/org/springframework/security/web/PortMapper.java b/web/src/main/java/org/springframework/security/web/PortMapper.java index eafe8597c6..24dec16e65 100644 --- a/web/src/main/java/org/springframework/security/web/PortMapper.java +++ b/web/src/main/java/org/springframework/security/web/PortMapper.java @@ -16,6 +16,8 @@ package org.springframework.security.web; +import org.jspecify.annotations.Nullable; + /** * PortMapper implementations provide callers with information about which * HTTP ports are associated with which HTTPS ports on the system, and vice versa. @@ -32,7 +34,7 @@ public interface PortMapper { * @param httpsPort * @return the HTTP port or null if unknown */ - Integer lookupHttpPort(Integer httpsPort); + @Nullable Integer lookupHttpPort(Integer httpsPort); /** * Locates the HTTPS port associated with the specified HTTP port. @@ -42,6 +44,6 @@ public interface PortMapper { * @param httpPort * @return the HTTPS port or null if unknown */ - Integer lookupHttpsPort(Integer httpPort); + @Nullable Integer lookupHttpsPort(Integer httpPort); } diff --git a/web/src/main/java/org/springframework/security/web/PortMapperImpl.java b/web/src/main/java/org/springframework/security/web/PortMapperImpl.java index 947cc93c62..10af27390d 100644 --- a/web/src/main/java/org/springframework/security/web/PortMapperImpl.java +++ b/web/src/main/java/org/springframework/security/web/PortMapperImpl.java @@ -19,6 +19,8 @@ package org.springframework.security.web; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -50,7 +52,7 @@ public class PortMapperImpl implements PortMapper { } @Override - public Integer lookupHttpPort(Integer httpsPort) { + public @Nullable Integer lookupHttpPort(Integer httpsPort) { for (Integer httpPort : this.httpsPortMappings.keySet()) { if (this.httpsPortMappings.get(httpPort).equals(httpsPort)) { return httpPort; @@ -60,7 +62,7 @@ public class PortMapperImpl implements PortMapper { } @Override - public Integer lookupHttpsPort(Integer httpPort) { + public @Nullable Integer lookupHttpsPort(Integer httpPort) { return this.httpsPortMappings.get(httpPort); } diff --git a/web/src/main/java/org/springframework/security/web/PortResolverImpl.java b/web/src/main/java/org/springframework/security/web/PortResolverImpl.java index 3e186a0399..235f39d06b 100644 --- a/web/src/main/java/org/springframework/security/web/PortResolverImpl.java +++ b/web/src/main/java/org/springframework/security/web/PortResolverImpl.java @@ -19,6 +19,7 @@ package org.springframework.security.web; import java.util.Locale; import jakarta.servlet.ServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -54,7 +55,7 @@ public class PortResolverImpl implements PortResolver { return (mappedPort != null) ? mappedPort : serverPort; } - private Integer getMappedPort(int serverPort, String scheme) { + private @Nullable Integer getMappedPort(int serverPort, String scheme) { if ("http".equals(scheme)) { return this.portMapper.lookupHttpPort(serverPort); } diff --git a/web/src/main/java/org/springframework/security/web/access/AccessDeniedHandlerImpl.java b/web/src/main/java/org/springframework/security/web/access/AccessDeniedHandlerImpl.java index 02423228e2..a0e220fcb0 100644 --- a/web/src/main/java/org/springframework/security/web/access/AccessDeniedHandlerImpl.java +++ b/web/src/main/java/org/springframework/security/web/access/AccessDeniedHandlerImpl.java @@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.http.HttpStatus; @@ -47,7 +48,7 @@ public class AccessDeniedHandlerImpl implements AccessDeniedHandler { protected static final Log logger = LogFactory.getLog(AccessDeniedHandlerImpl.class); - private String errorPage; + private @Nullable String errorPage; @Override public void handle(HttpServletRequest request, HttpServletResponse response, diff --git a/web/src/main/java/org/springframework/security/web/access/AuthorizationManagerWebInvocationPrivilegeEvaluator.java b/web/src/main/java/org/springframework/security/web/access/AuthorizationManagerWebInvocationPrivilegeEvaluator.java index 2468fbe702..b65782a6d4 100644 --- a/web/src/main/java/org/springframework/security/web/access/AuthorizationManagerWebInvocationPrivilegeEvaluator.java +++ b/web/src/main/java/org/springframework/security/web/access/AuthorizationManagerWebInvocationPrivilegeEvaluator.java @@ -18,6 +18,7 @@ package org.springframework.security.web.access; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationResult; @@ -38,7 +39,7 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator private final AuthorizationManager authorizationManager; - private ServletContext servletContext; + private @Nullable ServletContext servletContext; private HttpServletRequestTransformer requestTransformer = HttpServletRequestTransformer.IDENTITY; @@ -54,7 +55,8 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator } @Override - public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) { + public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method, + Authentication authentication) { FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext); HttpServletRequest httpRequest = this.requestTransformer.transform(filterInvocation.getHttpRequest()); AuthorizationResult result = this.authorizationManager.authorize(() -> authentication, httpRequest); diff --git a/web/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java b/web/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java index 0e5ab2a51f..f605557275 100644 --- a/web/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java +++ b/web/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java @@ -21,6 +21,7 @@ import java.util.Collection; import jakarta.servlet.ServletContext; 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.security.access.AccessDeniedException; @@ -46,7 +47,7 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv private final AbstractSecurityInterceptor securityInterceptor; - private ServletContext servletContext; + private @Nullable ServletContext servletContext; public DefaultWebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) { Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null"); @@ -86,7 +87,8 @@ public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPriv * @return true if access is allowed, false if denied */ @Override - public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) { + public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method, + Authentication authentication) { Assert.notNull(uri, "uri parameter is required"); FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext); Collection attributes = this.securityInterceptor.obtainSecurityMetadataSource() diff --git a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java index c51081e787..9be0de9478 100644 --- a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; @@ -169,7 +170,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes } private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, RuntimeException exception) throws IOException, ServletException { + FilterChain chain, @Nullable RuntimeException exception) throws IOException, ServletException { if (exception instanceof AuthenticationException) { handleAuthenticationException(request, response, chain, (AuthenticationException) exception); } diff --git a/web/src/main/java/org/springframework/security/web/access/PathPatternRequestTransformer.java b/web/src/main/java/org/springframework/security/web/access/PathPatternRequestTransformer.java index 4e146ba190..847dc8bcc8 100644 --- a/web/src/main/java/org/springframework/security/web/access/PathPatternRequestTransformer.java +++ b/web/src/main/java/org/springframework/security/web/access/PathPatternRequestTransformer.java @@ -21,6 +21,7 @@ import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.util.ServletRequestPathUtils; @@ -51,7 +52,7 @@ public final class PathPatternRequestTransformer } @Override - public Object getAttribute(String name) { + public @Nullable Object getAttribute(String name) { return this.attributes.get(name); } diff --git a/web/src/main/java/org/springframework/security/web/access/RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java b/web/src/main/java/org/springframework/security/web/access/RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java index 30fac413ce..45a9bc1bac 100644 --- a/web/src/main/java/org/springframework/security/web/access/RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java +++ b/web/src/main/java/org/springframework/security/web/access/RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java @@ -21,6 +21,7 @@ import java.util.List; import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; @@ -46,7 +47,7 @@ public final class RequestMatcherDelegatingWebInvocationPrivilegeEvaluator private final List>> delegates; - private ServletContext servletContext; + private @Nullable ServletContext servletContext; public RequestMatcherDelegatingWebInvocationPrivilegeEvaluator( List>> requestMatcherPrivilegeEvaluatorsEntries) { @@ -119,7 +120,8 @@ public final class RequestMatcherDelegatingWebInvocationPrivilegeEvaluator return true; } - private List getDelegate(String contextPath, String uri, String method) { + private List getDelegate(@Nullable String contextPath, String uri, + @Nullable String method) { FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext); HttpServletRequest request = filterInvocation.getHttpRequest(); for (RequestMatcherEntry> delegate : this.delegates) { diff --git a/web/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java b/web/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java index 2f35a47c20..adfb7c0bbe 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java @@ -22,6 +22,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.security.web.DefaultRedirectStrategy; @@ -79,7 +80,7 @@ public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint { this.redirectStrategy.sendRedirect(request, response, redirectUrl); } - protected abstract Integer getMappedPort(Integer mapFromPort); + protected abstract @Nullable Integer getMappedPort(Integer mapFromPort); protected final PortMapper getPortMapper() { return this.portMapper; diff --git a/web/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java b/web/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java index 1d9f05f65c..e82019f532 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.List; import jakarta.servlet.ServletException; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.ConfigAttribute; @@ -49,6 +51,7 @@ import org.springframework.util.Assert; * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated +@NullUnmarked public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean { public static final String ANY_CHANNEL = "ANY_CHANNEL"; @@ -76,7 +79,7 @@ public class ChannelDecisionManagerImpl implements ChannelDecisionManager, Initi } } - protected List getChannelProcessors() { + protected @Nullable List getChannelProcessors() { return this.channelProcessors; } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java b/web/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java index b14ae3bd47..ee2982fc64 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java @@ -27,6 +27,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.access.ConfigAttribute; @@ -88,8 +89,10 @@ import org.springframework.web.filter.GenericFilterBean; @Deprecated public class ChannelProcessingFilter extends GenericFilterBean { + @SuppressWarnings("NullAway.Init") private ChannelDecisionManager channelDecisionManager; + @SuppressWarnings("NullAway.Init") private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override @@ -128,14 +131,16 @@ public class ChannelProcessingFilter extends GenericFilterBean { if (attributes != null) { this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes)); this.channelDecisionManager.decide(filterInvocation, attributes); - if (filterInvocation.getResponse().isCommitted()) { + @Nullable HttpServletResponse channelResponse = filterInvocation.getResponse(); + Assert.notNull(channelResponse, "HttpServletResponse is required"); + if (channelResponse.isCommitted()) { return; } } chain.doFilter(request, response); } - protected ChannelDecisionManager getChannelDecisionManager() { + protected @Nullable ChannelDecisionManager getChannelDecisionManager() { return this.channelDecisionManager; } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java b/web/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java index 69d0fe9931..ba85920c48 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java @@ -20,6 +20,8 @@ import java.io.IOException; import java.util.Collection; import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.ConfigAttribute; @@ -63,7 +65,9 @@ public class InsecureChannelProcessor implements InitializingBean, ChannelProces for (ConfigAttribute attribute : config) { if (supports(attribute)) { if (invocation.getHttpRequest().isSecure()) { - this.entryPoint.commence(invocation.getRequest(), invocation.getResponse()); + @Nullable HttpServletResponse response = invocation.getResponse(); + Assert.notNull(response, "HttpServletResponse required"); + this.entryPoint.commence(invocation.getRequest(), response); } } } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java b/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java index a4cd77fd81..72c5cb01ba 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java @@ -16,6 +16,8 @@ package org.springframework.security.web.access.channel; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.PortMapper; /** @@ -38,7 +40,7 @@ public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint { } @Override - protected Integer getMappedPort(Integer mapFromPort) { + protected @Nullable Integer getMappedPort(Integer mapFromPort) { return getPortMapper().lookupHttpPort(mapFromPort); } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java b/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java index c7e113ac67..25b87ed6b4 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java @@ -16,6 +16,8 @@ package org.springframework.security.web.access.channel; +import org.jspecify.annotations.Nullable; + import org.springframework.security.web.PortMapper; /** @@ -39,7 +41,7 @@ public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint { } @Override - protected Integer getMappedPort(Integer mapFromPort) { + protected @Nullable Integer getMappedPort(Integer mapFromPort) { return getPortMapper().lookupHttpsPort(mapFromPort); } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java b/web/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java index 60de136c8a..2a1f985dad 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.Collection; import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.ConfigAttribute; @@ -63,7 +64,9 @@ public class SecureChannelProcessor implements InitializingBean, ChannelProcesso for (ConfigAttribute attribute : config) { if (supports(attribute)) { if (!invocation.getHttpRequest().isSecure()) { - this.entryPoint.commence(invocation.getRequest(), invocation.getResponse()); + HttpServletResponse response = invocation.getResponse(); + Assert.notNull(response, "HttpServletResponse is required"); + this.entryPoint.commence(invocation.getRequest(), response); } } } diff --git a/web/src/main/java/org/springframework/security/web/access/channel/package-info.java b/web/src/main/java/org/springframework/security/web/access/channel/package-info.java index 1b12cc0a06..353af41a71 100644 --- a/web/src/main/java/org/springframework/security/web/access/channel/package-info.java +++ b/web/src/main/java/org/springframework/security/web/access/channel/package-info.java @@ -19,4 +19,7 @@ *

* Most commonly used to enforce that requests are submitted over HTTP or HTTPS. */ +@NullMarked package org.springframework.security.web.access.channel; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java b/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java index a117a00b37..1d43028cde 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java @@ -19,6 +19,7 @@ package org.springframework.security.web.access.expression; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.expression.EvaluationContext; import org.springframework.security.web.FilterInvocation; @@ -53,7 +54,7 @@ abstract class AbstractVariableEvaluationContextPostProcessor private final HttpServletRequest request; - private Map variables; + private @Nullable Map variables; VariableEvaluationContext(EvaluationContext delegate, HttpServletRequest request) { super(delegate); @@ -61,7 +62,7 @@ abstract class AbstractVariableEvaluationContextPostProcessor } @Override - public Object lookupVariable(String name) { + public @Nullable Object lookupVariable(String name) { Object result = super.lookupVariable(name); if (result != null) { return result; diff --git a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java index cd7e558516..baadbeea13 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/DefaultHttpSecurityExpressionHandler.java @@ -46,6 +46,7 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres private String defaultRolePrefix = "ROLE_"; @Override + @SuppressWarnings("NullAway") // https://github.com/spring-projects/spring-framework/issues/35371 public EvaluationContext createEvaluationContext(Supplier authentication, RequestAuthorizationContext context) { WebSecurityExpressionRoot root = createSecurityExpressionRoot(authentication, context); @@ -56,13 +57,13 @@ public class DefaultHttpSecurityExpressionHandler extends AbstractSecurityExpres } @Override - protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, + protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication, RequestAuthorizationContext context) { return createSecurityExpressionRoot(() -> authentication, context); } - private WebSecurityExpressionRoot createSecurityExpressionRoot(Supplier authentication, - RequestAuthorizationContext context) { + private WebSecurityExpressionRoot createSecurityExpressionRoot( + Supplier authentication, RequestAuthorizationContext context) { WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, context.getRequest()); root.setRoleHierarchy(getRoleHierarchy()); root.setPermissionEvaluator(getPermissionEvaluator()); diff --git a/web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java b/web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java index 3712cb7cca..15b7c53a4b 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java @@ -16,6 +16,8 @@ package org.springframework.security.web.access.expression; +import org.jspecify.annotations.Nullable; + import org.springframework.security.access.expression.AbstractSecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionOperations; @@ -38,7 +40,7 @@ public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpress private String defaultRolePrefix = "ROLE_"; @Override - protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, + protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication, FilterInvocation fi) { WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi); root.setPermissionEvaluator(getPermissionEvaluator()); diff --git a/web/src/main/java/org/springframework/security/web/access/expression/DelegatingEvaluationContext.java b/web/src/main/java/org/springframework/security/web/access/expression/DelegatingEvaluationContext.java index 015a57f7ff..3a80df93b0 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/DelegatingEvaluationContext.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/DelegatingEvaluationContext.java @@ -18,6 +18,8 @@ package org.springframework.security.web.access.expression; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.expression.BeanResolver; import org.springframework.expression.ConstructorResolver; import org.springframework.expression.EvaluationContext; @@ -84,17 +86,17 @@ class DelegatingEvaluationContext implements EvaluationContext { } @Override - public BeanResolver getBeanResolver() { + public @Nullable BeanResolver getBeanResolver() { return this.delegate.getBeanResolver(); } @Override - public void setVariable(String name, Object value) { + public void setVariable(String name, @Nullable Object value) { this.delegate.setVariable(name, value); } @Override - public Object lookupVariable(String name) { + public @Nullable Object lookupVariable(String name) { return this.delegate.lookupVariable(name); } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java index 9a71c98480..6e3b140a51 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java @@ -16,6 +16,8 @@ package org.springframework.security.web.access.expression; +import org.jspecify.annotations.NullUnmarked; + import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.security.access.ConfigAttribute; @@ -32,6 +34,7 @@ import org.springframework.security.web.FilterInvocation; * {@link AuthorizationManager}. */ @Deprecated +@NullUnmarked class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor { private final Expression authorizeExpression; diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java index 4a4e7c8d58..4405fe25b5 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java @@ -20,6 +20,7 @@ import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.expression.EvaluationContext; import org.springframework.security.access.AccessDecisionVoter; @@ -66,7 +67,7 @@ public class WebExpressionVoter implements AccessDecisionVoter return ACCESS_DENIED; } - private WebExpressionConfigAttribute findConfigAttribute(Collection attributes) { + private @Nullable WebExpressionConfigAttribute findConfigAttribute(Collection attributes) { for (ConfigAttribute attribute : attributes) { if (attribute instanceof WebExpressionConfigAttribute) { return (WebExpressionConfigAttribute) attribute; diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java index a5e9516d6d..e375001820 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java @@ -19,6 +19,7 @@ package org.springframework.security.web.access.expression; import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; @@ -37,7 +38,7 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { */ public final HttpServletRequest request; - public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { + public WebSecurityExpressionRoot(@Nullable Authentication a, FilterInvocation fi) { this(() -> a, fi.getRequest()); } @@ -48,7 +49,9 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { * @param request the {@link HttpServletRequest} to use * @since 5.8 */ - public WebSecurityExpressionRoot(Supplier authentication, HttpServletRequest request) { + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1246 + public WebSecurityExpressionRoot(Supplier authentication, + HttpServletRequest request) { super(authentication); this.request = request; } diff --git a/web/src/main/java/org/springframework/security/web/access/expression/package-info.java b/web/src/main/java/org/springframework/security/web/access/expression/package-info.java index df7f1765a1..1ffbae53d2 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/package-info.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/package-info.java @@ -17,4 +17,7 @@ /** * Implementation of web security expressions. */ +@NullMarked package org.springframework.security.web.access.expression; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java index 3c1a4ee800..297ba2514d 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java @@ -26,6 +26,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; @@ -202,7 +203,7 @@ public class AuthorizationFilter extends GenericFilterBean { @Override public void publishAuthorizationEvent(Supplier authentication, T object, - AuthorizationResult result) { + @Nullable AuthorizationResult result) { } } diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java b/web/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java index d7954abdf0..5802901dfe 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java @@ -17,6 +17,7 @@ package org.springframework.security.web.access.intercept; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; @@ -99,7 +100,7 @@ public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvo } } } - return null; + return Collections.emptyList(); } @Override diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java b/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java index d45fddfd04..3d13141544 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java @@ -24,6 +24,7 @@ import jakarta.servlet.FilterConfig; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; @@ -48,7 +49,7 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor imple private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; - private FilterInvocationSecurityMetadataSource securityMetadataSource; + private @Nullable FilterInvocationSecurityMetadataSource securityMetadataSource; private boolean observeOncePerRequest = false; @@ -83,12 +84,12 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor imple invoke(new FilterInvocation(request, response, chain)); } - public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { + public @Nullable FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } @Override - public SecurityMetadataSource obtainSecurityMetadataSource() { + public @Nullable SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/RequestKey.java b/web/src/main/java/org/springframework/security/web/access/intercept/RequestKey.java index c67a5cbc56..4b822ce7a2 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/RequestKey.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/RequestKey.java @@ -16,6 +16,8 @@ package org.springframework.security.web.access.intercept; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -26,13 +28,13 @@ public class RequestKey { private final String url; - private final String method; + private final @Nullable String method; public RequestKey(String url) { this(url, null); } - public RequestKey(String url, String method) { + public RequestKey(String url, @Nullable String method) { Assert.notNull(url, "url cannot be null"); this.url = url; this.method = method; @@ -42,7 +44,7 @@ public class RequestKey { return this.url; } - String getMethod() { + @Nullable String getMethod() { return this.method; } diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java index 71d451c9f6..8de42775b8 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java @@ -64,7 +64,7 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho } @Override - public AuthorizationResult authorize(Supplier authentication, + public @Nullable AuthorizationResult authorize(Supplier authentication, HttpServletRequest request) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s", requestLine(request))); diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/package-info.java b/web/src/main/java/org/springframework/security/web/access/intercept/package-info.java index f80832b7a7..a5193858dd 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/package-info.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/package-info.java @@ -17,4 +17,7 @@ /** * Enforcement of security for HTTP requests, typically by the URL requested. */ +@NullMarked package org.springframework.security.web.access.intercept; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/access/package-info.java b/web/src/main/java/org/springframework/security/web/access/package-info.java index 2edb1460ea..00967c30ec 100644 --- a/web/src/main/java/org/springframework/security/web/access/package-info.java +++ b/web/src/main/java/org/springframework/security/web/access/package-info.java @@ -17,4 +17,7 @@ /** * Access-control related classes and packages. */ +@NullMarked package org.springframework.security.web.access; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java index 337a6fa574..5174e8d436 100644 --- a/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java +++ b/web/src/main/java/org/springframework/security/web/aot/hint/WebMvcSecurityRuntimeHints.java @@ -16,6 +16,8 @@ package org.springframework.security.web.aot.hint; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -33,7 +35,7 @@ import org.springframework.security.web.access.expression.WebSecurityExpressionR class WebMvcSecurityRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection() .registerType(WebSecurityExpressionRoot.class, (builder) -> builder .withMembers(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)); diff --git a/web/src/main/java/org/springframework/security/web/aot/hint/package-info.java b/web/src/main/java/org/springframework/security/web/aot/hint/package-info.java new file mode 100644 index 0000000000..cb8a21c2b1 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/aot/hint/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. + */ + +/** + * Runtime hints for AOT web package. + */ +@NullMarked +package org.springframework.security.web.aot.hint; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java index 9c7a0a2997..a817d7013e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -120,7 +121,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - protected ApplicationEventPublisher eventPublisher; + @Nullable protected ApplicationEventPublisher eventPublisher; protected AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); @@ -129,6 +130,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt "Please either configure an AuthenticationConverter or override attemptAuthentication when extending AbstractAuthenticationProcessingFilter"); }; + @SuppressWarnings("NullAway.Init") private AuthenticationManager authenticationManager; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); @@ -155,7 +157,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt * @param defaultFilterProcessesUrl the default value for filterProcessesUrl. */ protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) { - setFilterProcessesUrl(defaultFilterProcessesUrl); + this(pathPattern(defaultFilterProcessesUrl)); } /** @@ -177,7 +179,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt */ protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) { - setFilterProcessesUrl(defaultFilterProcessesUrl); + this(pathPattern(defaultFilterProcessesUrl)); setAuthenticationManager(authenticationManager); } @@ -305,7 +307,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt * @return the authenticated user token, or null if authentication is incomplete. * @throws AuthenticationException if authentication fails. */ - public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + public @Nullable Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { Authentication authentication = this.authenticationConverter.convert(request); if (authentication == null) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java index d66f47ecce..0ae3f32d84 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationTargetUrlRequestHandler.java @@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.security.core.Authentication; @@ -62,7 +63,7 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { protected final Log logger = LogFactory.getLog(this.getClass()); - private String targetUrlParameter = null; + private @Nullable String targetUrlParameter = null; private String defaultTargetUrl = "/"; @@ -81,8 +82,8 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { *

* The redirect will not be performed if the response has already been committed. */ - protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { + protected void handle(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException, ServletException { String targetUrl = determineTargetUrl(request, response, authentication); if (response.isCommitted()) { this.logger.debug(LogMessage.format("Did not redirect to %s since response already committed.", targetUrl)); @@ -96,7 +97,7 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { * @since 5.2 */ protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { + @Nullable Authentication authentication) { return determineTargetUrl(request, response); } @@ -119,7 +120,7 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { return this.defaultTargetUrl; } - private String getTargetUrlParameterValue(HttpServletRequest request) { + private @Nullable String getTargetUrlParameterValue(HttpServletRequest request) { if (this.targetUrlParameter == null) { return null; } @@ -133,7 +134,7 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { return this.defaultTargetUrl; } - private void trace(String msg, String... msgParts) { + private void trace(String msg, @Nullable String... msgParts) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format(msg, msgParts)); } @@ -189,7 +190,7 @@ public abstract class AbstractAuthenticationTargetUrlRequestHandler { this.targetUrlParameter = targetUrlParameter; } - protected String getTargetUrlParameter() { + protected @Nullable String getTargetUrlParameter() { return this.targetUrlParameter; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationConverter.java index 8329327f83..020f8d0dfd 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationConverter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; @@ -35,6 +36,6 @@ import org.springframework.security.core.AuthenticationException; */ public interface AuthenticationConverter { - Authentication convert(HttpServletRequest request); + @Nullable Authentication convert(HttpServletRequest request); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java index ee1364d0d3..5366ebd84b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; @@ -218,7 +219,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { this.successHandler.onAuthenticationSuccess(request, response, chain, authentication); } - private Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + private @Nullable Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, ServletException { Authentication authentication = this.authenticationConverter.convert(request); if (authentication == null) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java index 1729363054..1de5ff5c92 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationConverter.java @@ -19,6 +19,7 @@ package org.springframework.security.web.authentication; import java.util.List; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -46,7 +47,7 @@ public final class DelegatingAuthenticationConverter implements AuthenticationCo } @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { for (AuthenticationConverter delegate : this.delegates) { Authentication authentication = delegate.convert(request); if (authentication != null) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationEntryPoint.java index 8f036b02ba..bb8d845475 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationEntryPoint.java @@ -66,6 +66,7 @@ public class DelegatingAuthenticationEntryPoint implements AuthenticationEntryPo private final LinkedHashMap entryPoints; + @SuppressWarnings("NullAway.Init") private AuthenticationEntryPoint defaultEntryPoint; public DelegatingAuthenticationEntryPoint(LinkedHashMap entryPoints) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java index 95e2e45212..5aba972225 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.log.LogMessage; @@ -187,7 +188,8 @@ public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoin * Builds a URL to redirect the supplied request to HTTPS. Used to redirect the * current request to HTTPS, before doing a forward to the login page. */ - protected String buildHttpsRedirectUrlForRequest(HttpServletRequest request) throws IOException, ServletException { + protected @Nullable String buildHttpsRedirectUrlForRequest(HttpServletRequest request) + throws IOException, ServletException { int serverPort = this.portResolver.getServerPort(request); Integer httpsPort = this.portMapper.lookupHttpsPort(serverPort); if (httpsPort != null) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/NullRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/NullRememberMeServices.java index afccdaca3f..64ba5090a6 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/NullRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/NullRememberMeServices.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -32,7 +33,7 @@ import org.springframework.security.core.Authentication; public class NullRememberMeServices implements RememberMeServices { @Override - public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { + public @Nullable Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { return null; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/RememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/RememberMeServices.java index b53cb0ffaf..2ef3493b47 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/RememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/RememberMeServices.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -69,7 +70,7 @@ public interface RememberMeServices { * @return a valid authentication object, or null if the request should * not be authenticated */ - Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); + @Nullable Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); /** * Called whenever an interactive authentication attempt was made, but the credentials diff --git a/web/src/main/java/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.java b/web/src/main/java/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.java index ce906404a3..a9aa662e3b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/SimpleUrlAuthenticationFailureHandler.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; @@ -50,7 +51,7 @@ public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFail protected final Log logger = LogFactory.getLog(getClass()); - private String defaultFailureUrl; + private @Nullable String defaultFailureUrl; private boolean forwardToDestination = false; diff --git a/web/src/main/java/org/springframework/security/web/authentication/WebAuthenticationDetails.java b/web/src/main/java/org/springframework/security/web/authentication/WebAuthenticationDetails.java index fffe50ff59..b4444d1e93 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/WebAuthenticationDetails.java +++ b/web/src/main/java/org/springframework/security/web/authentication/WebAuthenticationDetails.java @@ -21,6 +21,7 @@ import java.util.Objects; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; /** * A holder of selected HTTP details related to a web authentication request. @@ -34,7 +35,7 @@ public class WebAuthenticationDetails implements Serializable { private final String remoteAddress; - private final String sessionId; + private final @Nullable String sessionId; /** * Records the remote address and will also set the session Id if a session already @@ -51,12 +52,12 @@ public class WebAuthenticationDetails implements Serializable { * @param sessionId session id * @since 5.7 */ - public WebAuthenticationDetails(String remoteAddress, String sessionId) { + public WebAuthenticationDetails(String remoteAddress, @Nullable String sessionId) { this.remoteAddress = remoteAddress; this.sessionId = sessionId; } - private static String extractSessionId(HttpServletRequest request) { + private static @Nullable String extractSessionId(HttpServletRequest request) { HttpSession session = request.getSession(false); return (session != null) ? session.getId() : null; } @@ -74,7 +75,7 @@ public class WebAuthenticationDetails implements Serializable { * from. * @return the session ID */ - public String getSessionId() { + public @Nullable String getSessionId() { return this.sessionId; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/CompositeLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/CompositeLogoutHandler.java index 4a0ca50c66..7d3a2599c8 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/CompositeLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/CompositeLogoutHandler.java @@ -21,6 +21,7 @@ import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -49,7 +50,8 @@ public final class CompositeLogoutHandler implements LogoutHandler { } @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { for (LogoutHandler handler : this.logoutHandlers) { handler.logout(request, response, authentication); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java index b90bd0e712..c1ed81fa4e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.java @@ -23,6 +23,7 @@ import java.util.function.Function; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -72,7 +73,8 @@ public final class CookieClearingLogoutHandler implements LogoutHandler { } @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { this.cookiesToClear.forEach((f) -> response.addCookie(f.apply(request))); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/DelegatingLogoutSuccessHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/DelegatingLogoutSuccessHandler.java index 67637c9540..d1d09a867d 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/DelegatingLogoutSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/DelegatingLogoutSuccessHandler.java @@ -23,6 +23,7 @@ import java.util.Map; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -39,7 +40,7 @@ public class DelegatingLogoutSuccessHandler implements LogoutSuccessHandler { private final LinkedHashMap matcherToHandler; - private LogoutSuccessHandler defaultLogoutSuccessHandler; + private @Nullable LogoutSuccessHandler defaultLogoutSuccessHandler; public DelegatingLogoutSuccessHandler(LinkedHashMap matcherToHandler) { Assert.notEmpty(matcherToHandler, "matcherToHandler cannot be null"); @@ -47,8 +48,8 @@ public class DelegatingLogoutSuccessHandler implements LogoutSuccessHandler { } @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException, ServletException { for (Map.Entry entry : this.matcherToHandler.entrySet()) { RequestMatcher matcher = entry.getKey(); if (matcher.matches(request)) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/ForwardLogoutSuccessHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/ForwardLogoutSuccessHandler.java index b219dd2659..3dc74fa057 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/ForwardLogoutSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/ForwardLogoutSuccessHandler.java @@ -21,6 +21,7 @@ import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.util.UrlUtils; @@ -47,8 +48,8 @@ public class ForwardLogoutSuccessHandler implements LogoutSuccessHandler { } @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException, ServletException { request.getRequestDispatcher(this.targetUrl).forward(request, response); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/HeaderWriterLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/HeaderWriterLogoutHandler.java index 906a977abc..e04ab8f165 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/HeaderWriterLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/HeaderWriterLogoutHandler.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.logout; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.header.HeaderWriter; @@ -42,7 +43,8 @@ public final class HeaderWriterLogoutHandler implements LogoutHandler { } @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { this.headerWriter.writeHeaders(request, response); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/HttpStatusReturningLogoutSuccessHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/HttpStatusReturningLogoutSuccessHandler.java index 75c11d3b86..1716bc141e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/HttpStatusReturningLogoutSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/HttpStatusReturningLogoutSuccessHandler.java @@ -20,6 +20,7 @@ import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; @@ -61,8 +62,8 @@ public class HttpStatusReturningLogoutSuccessHandler implements LogoutSuccessHan * . Sets the status on the {@link HttpServletResponse}. */ @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException { + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException { response.setStatus(this.httpStatusToReturn.value()); response.getWriter().flush(); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java index 94bafca971..23fc47d470 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutFilter.java @@ -69,6 +69,7 @@ public class LogoutFilter extends GenericFilterBean { * intended to perform the actual logout functionality (such as clearing the security * context, invalidating the session, etc.). */ + @SuppressWarnings("NullAway") // Dataflow analysis limitation public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { this.handler = new CompositeLogoutHandler(handlers); Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null"); @@ -76,6 +77,7 @@ public class LogoutFilter extends GenericFilterBean { setFilterProcessesUrl("/logout"); } + @SuppressWarnings("NullAway") // Dataflow analysis limitation public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) { this.handler = new CompositeLogoutHandler(handlers); Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl), diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutHandler.java index 7e78ca2bb5..a236d202d2 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutHandler.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.logout; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -37,6 +38,6 @@ public interface LogoutHandler { * @param response the HTTP response * @param authentication the current principal details */ - void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication); + void logout(HttpServletRequest request, HttpServletResponse response, @Nullable Authentication authentication); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java index a7e5797527..dc7abcdcbb 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.logout; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -32,10 +33,11 @@ import org.springframework.security.core.Authentication; */ public final class LogoutSuccessEventPublishingLogoutHandler implements LogoutHandler, ApplicationEventPublisherAware { - private ApplicationEventPublisher eventPublisher; + private @Nullable ApplicationEventPublisher eventPublisher; @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { if (this.eventPublisher == null) { return; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessHandler.java index 85c24913bc..f752f0deca 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessHandler.java @@ -21,6 +21,7 @@ import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -37,7 +38,7 @@ import org.springframework.security.core.Authentication; */ public interface LogoutSuccessHandler { - void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException; + void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException, ServletException; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.java index ac0f598baf..92f9214143 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; 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.security.core.Authentication; @@ -64,7 +65,8 @@ public class SecurityContextLogoutHandler implements LogoutHandler { * @param authentication not used (can be null) */ @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { Assert.notNull(request, "HttpServletRequest required"); if (this.invalidateHttpSession) { HttpSession session = request.getSession(false); diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/SimpleUrlLogoutSuccessHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/SimpleUrlLogoutSuccessHandler.java index 0c981529c7..f84a2a7e17 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/SimpleUrlLogoutSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/SimpleUrlLogoutSuccessHandler.java @@ -21,6 +21,7 @@ import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler; @@ -36,8 +37,8 @@ public class SimpleUrlLogoutSuccessHandler extends AbstractAuthenticationTargetU implements LogoutSuccessHandler { @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) throws IOException, ServletException { super.handle(request, response, authentication); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/logout/package-info.java index d24ccef3a9..1df716b7d4 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/logout/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/package-info.java @@ -17,4 +17,7 @@ /** * Logout functionality based around a filter which handles a specific logout URL. */ +@NullMarked package org.springframework.security.web.authentication.logout; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java b/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java index 299689f616..e9ce61be08 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/DefaultGenerateOneTimeTokenRequestResolver.java @@ -19,6 +19,7 @@ package org.springframework.security.web.authentication.ott; import java.time.Duration; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; import org.springframework.util.Assert; @@ -38,7 +39,7 @@ public final class DefaultGenerateOneTimeTokenRequestResolver implements Generat private Duration expiresIn = DEFAULT_EXPIRES_IN; @Override - public GenerateOneTimeTokenRequest resolve(HttpServletRequest request) { + public @Nullable GenerateOneTimeTokenRequest resolve(HttpServletRequest request) { String username = request.getParameter("username"); if (!StringUtils.hasText(username)) { return null; diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java index b53c31dd18..b75c1a1977 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/GenerateOneTimeTokenFilter.java @@ -74,11 +74,11 @@ public final class GenerateOneTimeTokenFilter extends OncePerRequestFilter { return; } GenerateOneTimeTokenRequest generateRequest = this.requestResolver.resolve(request); - OneTimeToken ott = this.tokenService.generate(generateRequest); if (generateRequest == null) { filterChain.doFilter(request, response); return; } + OneTimeToken ott = this.tokenService.generate(generateRequest); this.tokenGenerationSuccessHandler.handle(request, response, ott); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/OneTimeTokenAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/ott/OneTimeTokenAuthenticationConverter.java index 024c6cef00..95edad2cea 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ott/OneTimeTokenAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/OneTimeTokenAuthenticationConverter.java @@ -19,6 +19,7 @@ package org.springframework.security.web.authentication.ott; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken; import org.springframework.security.core.Authentication; @@ -39,7 +40,7 @@ public class OneTimeTokenAuthenticationConverter implements AuthenticationConver private final Log logger = LogFactory.getLog(getClass()); @Override - public Authentication convert(HttpServletRequest request) { + public @Nullable Authentication convert(HttpServletRequest request) { String token = request.getParameter("token"); if (!StringUtils.hasText(token)) { this.logger.debug("No token found in request"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/ott/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/ott/package-info.java new file mode 100644 index 0000000000..1ba7280cb5 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/ott/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. + */ + +/** + * Package for One Time Token usage. + */ +@NullMarked +package org.springframework.security.web.authentication.ott; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/package-info.java index 76d09ae93f..453da22b34 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/package-info.java @@ -18,4 +18,7 @@ * Authentication processing mechanisms, which respond to the submission of authentication * credentials using various protocols (eg BASIC, CAS, form login etc). */ +@NullMarked package org.springframework.security.web.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java b/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java index fa38a45362..8784f8e900 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java +++ b/web/src/main/java/org/springframework/security/web/authentication/password/HaveIBeenPwnedRestApiPasswordChecker.java @@ -25,6 +25,7 @@ import java.util.Locale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.password.CompromisedPasswordChecker; import org.springframework.security.authentication.password.CompromisedPasswordDecision; @@ -60,7 +61,7 @@ public final class HaveIBeenPwnedRestApiPasswordChecker implements CompromisedPa } @Override - public CompromisedPasswordDecision check(String password) { + public CompromisedPasswordDecision check(@Nullable String password) { if (password == null) { return new CompromisedPasswordDecision(false); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/password/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/password/package-info.java new file mode 100644 index 0000000000..c6fd4ec515 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/password/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. + */ + +/** + * Classes for Password APIs. + */ +@NullMarked +package org.springframework.security.web.authentication.password; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java index b93196ffcd..8189cc2b1f 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java @@ -25,6 +25,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -96,11 +97,12 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - private ApplicationEventPublisher eventPublisher = null; + private @Nullable ApplicationEventPublisher eventPublisher = null; private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private AuthenticationManager authenticationManager = null; + @SuppressWarnings("NullAway.Init") + private AuthenticationManager authenticationManager; private boolean continueFilterChainOnUnsuccessfulAuthentication = true; @@ -108,9 +110,9 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi private boolean invalidateSessionOnPrincipalChange = true; - private AuthenticationSuccessHandler authenticationSuccessHandler = null; + private @Nullable AuthenticationSuccessHandler authenticationSuccessHandler = null; - private AuthenticationFailureHandler authenticationFailureHandler = null; + private @Nullable AuthenticationFailureHandler authenticationFailureHandler = null; private RequestMatcher requiresAuthenticationRequestMatcher = new PreAuthenticatedProcessingRequestMatcher(); @@ -357,14 +359,14 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi /** * Override to extract the principal information from the current request */ - protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); + protected abstract @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request); /** * Override to extract the credentials (if applicable) from the current request. * Should not return null for a valid principal, though some implementations may * return a dummy value. */ - protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request); + protected abstract @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request); /** * Request matcher for default auth check logic diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java index a0fffc6d45..486847778b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.preauth; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Ordered; @@ -51,6 +52,7 @@ public class PreAuthenticatedAuthenticationProvider implements AuthenticationPro private static final Log logger = LogFactory.getLog(PreAuthenticatedAuthenticationProvider.class); + @SuppressWarnings("NullAway.Init") private AuthenticationUserDetailsService preAuthenticatedUserDetailsService; private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); @@ -74,7 +76,7 @@ public class PreAuthenticatedAuthenticationProvider implements AuthenticationPro * be ignored to allow other providers to authenticate it. */ @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { return null; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java index dc1a15e89e..fefda2ca49 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationToken.java @@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.preauth; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -34,7 +36,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT private final Object principal; - private final Object credentials; + private final @Nullable Object credentials; /** * Constructor used for an authentication request. The @@ -43,7 +45,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT * @param aPrincipal The pre-authenticated principal * @param aCredentials The pre-authenticated credentials */ - public PreAuthenticatedAuthenticationToken(Object aPrincipal, Object aCredentials) { + public PreAuthenticatedAuthenticationToken(Object aPrincipal, @Nullable Object aCredentials) { super(null); this.principal = aPrincipal; this.credentials = aCredentials; @@ -56,7 +58,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT * @param aPrincipal The authenticated principal * @param anAuthorities The granted authorities */ - public PreAuthenticatedAuthenticationToken(Object aPrincipal, Object aCredentials, + public PreAuthenticatedAuthenticationToken(Object aPrincipal, @Nullable Object aCredentials, Collection anAuthorities) { super(anAuthorities); this.principal = aPrincipal; @@ -68,7 +70,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT * Get the credentials */ @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return this.credentials; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestAttributeAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestAttributeAuthenticationFilter.java index f6c0afae24..3afd969bed 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestAttributeAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestAttributeAuthenticationFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.preauth; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -46,7 +47,7 @@ public class RequestAttributeAuthenticationFilter extends AbstractPreAuthenticat private String principalEnvironmentVariable = "REMOTE_USER"; - private String credentialsEnvironmentVariable; + private @Nullable String credentialsEnvironmentVariable; private boolean exceptionIfVariableMissing = true; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestHeaderAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestHeaderAuthenticationFilter.java index 267eb16c69..c2c51e9a48 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestHeaderAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/RequestHeaderAuthenticationFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.preauth; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -47,7 +48,7 @@ public class RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedP private String principalRequestHeader = "SM_USER"; - private String credentialsRequestHeader; + private @Nullable String credentialsRequestHeader; private boolean exceptionIfHeaderMissing = true; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.java index d6b3c2d270..83b6e52af6 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.java @@ -52,6 +52,7 @@ public class J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource implements /** * The role attributes returned by the configured {@code MappableAttributesRetriever} */ + @SuppressWarnings("NullAway.Init") protected Set j2eeMappableRoles; protected Attributes2GrantedAuthoritiesMapper j2eeUserRoles2GrantedAuthoritiesMapper = new SimpleAttributes2GrantedAuthoritiesMapper(); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eePreAuthenticatedProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eePreAuthenticatedProcessingFilter.java index 51ab4de19f..297d772210 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eePreAuthenticatedProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eePreAuthenticatedProcessingFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.preauth.j2ee; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; @@ -35,7 +36,7 @@ public class J2eePreAuthenticatedProcessingFilter extends AbstractPreAuthenticat * Return the J2EE user name. */ @Override - protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) { + protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) { Object principal = (httpRequest.getUserPrincipal() != null) ? httpRequest.getUserPrincipal().getName() : null; this.logger.debug(LogMessage.format("PreAuthenticated J2EE principal: %s", principal)); return principal; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlMappableAttributesRetriever.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlMappableAttributesRetriever.java index 2aa361563a..d7f4bcb618 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlMappableAttributesRetriever.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlMappableAttributesRetriever.java @@ -32,6 +32,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -60,9 +61,9 @@ public class WebXmlMappableAttributesRetriever protected final Log logger = LogFactory.getLog(getClass()); - private ResourceLoader resourceLoader; + private @Nullable ResourceLoader resourceLoader; - private Set mappableAttributes; + private Set mappableAttributes = new HashSet<>(); @Override public void setResourceLoader(ResourceLoader resourceLoader) { @@ -81,6 +82,7 @@ public class WebXmlMappableAttributesRetriever @Override public void afterPropertiesSet() throws Exception { + Assert.notNull(this.resourceLoader, "resourceLoader cannot be null"); Resource webXml = this.resourceLoader.getResource("/WEB-INF/web.xml"); Document doc = getDocument(webXml.getInputStream()); NodeList webApp = doc.getElementsByTagName("web-app"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/package-info.java index 8eac34786a..4099bc0d71 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/package-info.java @@ -21,4 +21,7 @@ * into the security methods exposed by {@code HttpServletRequest} to build * {@code Authentication} object for the user. */ +@NullMarked package org.springframework.security.web.authentication.preauth.j2ee; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/package-info.java index 6987e479a8..6f77178fc7 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/package-info.java @@ -18,4 +18,7 @@ * Support for "pre-authenticated" scenarios, where Spring Security assumes the incoming * request has already been authenticated by some externally configured system. */ +@NullMarked package org.springframework.security.web.authentication.preauth; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/DefaultWASUsernameAndGroupsExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/DefaultWASUsernameAndGroupsExtractor.java index 155b137ae5..7d97500b6f 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/DefaultWASUsernameAndGroupsExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/DefaultWASUsernameAndGroupsExtractor.java @@ -30,6 +30,7 @@ import javax.security.auth.Subject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; @@ -50,16 +51,16 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups private static final String USER_REGISTRY = "UserRegistry"; - private static Method getRunAsSubject = null; + private static @Nullable Method getRunAsSubject = null; - private static Method getGroupsForUser = null; + private static @Nullable Method getGroupsForUser = null; - private static Method getSecurityName = null; + private static @Nullable Method getSecurityName = null; - private static Method narrow = null; + private static @Nullable Method narrow = null; // SEC-803 - private static Class wsCredentialClass = null; + private static @Nullable Class wsCredentialClass = null; @Override public List getGroupsForCurrentUser() { @@ -67,7 +68,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups } @Override - public String getCurrentUserName() { + public @Nullable String getCurrentUserName() { return getSecurityName(getRunAsSubject()); } @@ -76,7 +77,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups * @param subject The subject for which to retrieve the security name * @return String the security name for the given subject */ - private static String getSecurityName(final Subject subject) { + private static @Nullable String getSecurityName(final Subject subject) { logger.debug(LogMessage.format("Determining Websphere security name for subject %s", subject)); String userSecurityName = null; if (subject != null) { @@ -116,7 +117,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups * @return the WebSphere group names for the given security name */ @SuppressWarnings("unchecked") - private static List getWebSphereGroups(final String securityName) { + private static List getWebSphereGroups(final @Nullable String securityName) { Context context = null; try { // TODO: Cache UserRegistry object @@ -140,7 +141,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups } } - private static void closeContext(Context context) { + private static void closeContext(@Nullable Context context) { try { if (context != null) { context.close(); @@ -151,7 +152,7 @@ final class DefaultWASUsernameAndGroupsExtractor implements WASUsernameAndGroups } } - private static Object invokeMethod(Method method, Object instance, Object... args) { + private static Object invokeMethod(Method method, @Nullable Object instance, Object... args) { try { return method.invoke(instance, args); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WASUsernameAndGroupsExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WASUsernameAndGroupsExtractor.java index cbf2faf0a9..f25ab6781a 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WASUsernameAndGroupsExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WASUsernameAndGroupsExtractor.java @@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.preauth.websphere; import java.util.List; +import org.jspecify.annotations.Nullable; + /** * Provides indirection between classes using websphere and the actual container * interaction, allowing for easier unit testing. @@ -31,6 +33,6 @@ interface WASUsernameAndGroupsExtractor { List getGroupsForCurrentUser(); - String getCurrentUserName(); + @Nullable String getCurrentUserName(); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java index 8357b651a3..ccce1ac0bb 100755 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/WebSpherePreAuthenticatedProcessingFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.preauth.websphere; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; @@ -50,7 +51,7 @@ public class WebSpherePreAuthenticatedProcessingFilter extends AbstractPreAuthen * Return the WebSphere user name. */ @Override - protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) { + protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest httpRequest) { Object principal = this.wasHelper.getCurrentUserName(); this.logger.debug(LogMessage.format("PreAuthenticated WebSphere principal: %s", principal)); return principal; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/package-info.java index 9aa4c1b317..c2ba34e164 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/websphere/package-info.java @@ -17,4 +17,7 @@ /** * Websphere-specific pre-authentication classes. */ +@NullMarked package org.springframework.security.web.authentication.preauth.websphere; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java index ce45091fa5..e1cce04bb8 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java @@ -54,6 +54,7 @@ public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, private Pattern subjectDnPattern; + @SuppressWarnings("NullAway") // Dataflow analysis limitation public SubjectDnX509PrincipalExtractor() { setSubjectDnRegex("CN=(.*?)(?:,|$)"); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java index 40ae9608b5..1b0b42f6cc 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java @@ -19,6 +19,7 @@ package org.springframework.security.web.authentication.preauth.x509; import java.security.cert.X509Certificate; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; @@ -31,17 +32,17 @@ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessing private X509PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); @Override - protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { + protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) { X509Certificate cert = extractClientCertificate(request); return (cert != null) ? this.principalExtractor.extractPrincipal(cert) : null; } @Override - protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { + protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) { return extractClientCertificate(request); } - private X509Certificate extractClientCertificate(HttpServletRequest request) { + private @Nullable X509Certificate extractClientCertificate(HttpServletRequest request) { X509Certificate[] certs = (X509Certificate[]) request.getAttribute("jakarta.servlet.request.X509Certificate"); if (certs != null && certs.length > 0) { this.logger.debug(LogMessage.format("X.509 client authentication certificate:%s", certs[0])); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/package-info.java index 8fa7db7757..0bfb295344 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/package-info.java @@ -19,4 +19,7 @@ * the servlet container through the {@code jakarta.servlet.request.X509Certificate} * property. */ +@NullMarked package org.springframework.security.web.authentication.preauth.x509; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index cefe4c08a6..bf21ce6a54 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -28,6 +28,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; @@ -84,7 +85,7 @@ public abstract class AbstractRememberMeServices private String cookieName = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY; - private String cookieDomain; + private @Nullable String cookieDomain; private String parameter = DEFAULT_PARAMETER; @@ -94,7 +95,7 @@ public abstract class AbstractRememberMeServices private int tokenValiditySeconds = TWO_WEEKS_S; - private Boolean useSecureCookie = null; + private @Nullable Boolean useSecureCookie = null; private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @@ -123,7 +124,7 @@ public abstract class AbstractRememberMeServices * which in turn is used to create a valid authentication token. */ @Override - public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { + public @Nullable Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { String rememberMeCookie = extractRememberMeCookie(request); if (rememberMeCookie == null) { return null; @@ -168,7 +169,7 @@ public abstract class AbstractRememberMeServices * @param request the submitted request which is to be authenticated * @return the cookie value (if present), null otherwise. */ - protected String extractRememberMeCookie(HttpServletRequest request) { + protected @Nullable String extractRememberMeCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if ((cookies == null) || (cookies.length == 0)) { return null; @@ -390,7 +391,8 @@ public abstract class AbstractRememberMeServices * {@code cancelCookie()}. */ @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { this.logger.debug(LogMessage .of(() -> "Logout of user " + ((authentication != null) ? authentication.getName() : "Unknown"))); cancelCookie(request, response); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/InMemoryTokenRepositoryImpl.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/InMemoryTokenRepositoryImpl.java index 72d52ce7d3..2021345fbb 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/InMemoryTokenRepositoryImpl.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/InMemoryTokenRepositoryImpl.java @@ -21,6 +21,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.DataIntegrityViolationException; /** @@ -45,6 +47,9 @@ public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository { @Override public synchronized void updateToken(String series, String tokenValue, Date lastUsed) { PersistentRememberMeToken token = getTokenForSeries(series); + if (token == null) { + throw new IllegalArgumentException("Token for series '" + series + "' does not exist"); + } PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), series, tokenValue, new Date()); // Store it, overwriting the existing one. @@ -52,7 +57,7 @@ public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository { } @Override - public synchronized PersistentRememberMeToken getTokenForSeries(String seriesId) { + public synchronized @Nullable PersistentRememberMeToken getTokenForSeries(String seriesId) { return this.seriesTokens.get(seriesId); } @@ -62,7 +67,7 @@ public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository { while (series.hasNext()) { String seriesId = series.next(); PersistentRememberMeToken token = this.seriesTokens.get(seriesId); - if (username.equals(token.getUsername())) { + if (token != null && username.equals(token.getUsername())) { series.remove(); } } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java index 5df0794a69..055af5f8cd 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java @@ -20,10 +20,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; +import org.jspecify.annotations.Nullable; + import org.springframework.core.log.LogMessage; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.support.JdbcDaoSupport; /** @@ -63,19 +66,19 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten @Override protected void initDao() { if (this.createTableOnStartup) { - getJdbcTemplate().execute(CREATE_TABLE_SQL); + getTemplate().execute(CREATE_TABLE_SQL); } } @Override public void createNewToken(PersistentRememberMeToken token) { - getJdbcTemplate().update(this.insertTokenSql, token.getUsername(), token.getSeries(), token.getTokenValue(), + getTemplate().update(this.insertTokenSql, token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()); } @Override public void updateToken(String series, String tokenValue, Date lastUsed) { - getJdbcTemplate().update(this.updateTokenSql, tokenValue, lastUsed, series); + getTemplate().update(this.updateTokenSql, tokenValue, lastUsed, series); } /** @@ -88,9 +91,9 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten * occurred. */ @Override - public PersistentRememberMeToken getTokenForSeries(String seriesId) { + public @Nullable PersistentRememberMeToken getTokenForSeries(String seriesId) { try { - return getJdbcTemplate().queryForObject(this.tokensBySeriesSql, this::createRememberMeToken, seriesId); + return getTemplate().queryForObject(this.tokensBySeriesSql, this::createRememberMeToken, seriesId); } catch (EmptyResultDataAccessException ex) { this.logger.debug(LogMessage.format("Querying token for series '%s' returned no results.", seriesId), ex); @@ -112,7 +115,7 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten @Override public void removeUserTokens(String username) { - getJdbcTemplate().update(this.removeUserTokensSql, username); + getTemplate().update(this.removeUserTokensSql, username); } /** @@ -124,4 +127,12 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten this.createTableOnStartup = createTableOnStartup; } + private JdbcTemplate getTemplate() { + @Nullable JdbcTemplate result = super.getJdbcTemplate(); + if (result == null) { + throw new IllegalStateException("JdbcTemplate was removed"); + } + return result; + } + } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java index ce1d134cad..e6359451ff 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java @@ -23,6 +23,7 @@ import java.util.Date; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.core.Authentication; @@ -156,7 +157,8 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe } @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { super.logout(request, response, authentication); if (authentication != null) { this.tokenRepository.removeUserTokens(authentication.getName()); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenRepository.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenRepository.java index ec07842801..5e633e05de 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenRepository.java @@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.rememberme; import java.util.Date; +import org.jspecify.annotations.Nullable; + /** * The abstraction used by {@link PersistentTokenBasedRememberMeServices} to store the * persistent login tokens for a user. @@ -33,7 +35,7 @@ public interface PersistentTokenRepository { void updateToken(String series, String tokenValue, Date lastUsed); - PersistentRememberMeToken getTokenForSeries(String seriesId); + @Nullable PersistentRememberMeToken getTokenForSeries(String seriesId); void removeUserTokens(String username); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java index 359b233aea..49ad13b881 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -73,9 +74,9 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - private ApplicationEventPublisher eventPublisher; + private @Nullable ApplicationEventPublisher eventPublisher; - private AuthenticationSuccessHandler successHandler; + private @Nullable AuthenticationSuccessHandler successHandler; private AuthenticationManager authenticationManager; @@ -114,23 +115,23 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements chain.doFilter(request, response); return; } - Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response); - if (rememberMeAuth != null) { + Authentication remembermeToken = this.rememberMeServices.autoLogin(request, response); + if (remembermeToken != null) { // Attempt authentication via AuthenticationManager try { - rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth); + Authentication rememberMeAuth = this.authenticationManager.authenticate(remembermeToken); this.sessionStrategy.onAuthentication(rememberMeAuth, request, response); // Store to SecurityContextHolder SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(rememberMeAuth); this.securityContextHolderStrategy.setContext(context); onSuccessfulAuthentication(request, response, rememberMeAuth); - this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '" - + this.securityContextHolderStrategy.getContext().getAuthentication() + "'")); + this.logger.debug(LogMessage + .of(() -> "SecurityContextHolder populated with remember-me token: '" + rememberMeAuth + "'")); this.securityContextRepository.saveContext(context, request, response); if (this.eventPublisher != null) { - this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( - this.securityContextHolderStrategy.getContext().getAuthentication(), this.getClass())); + this.eventPublisher + .publishEvent(new InteractiveAuthenticationSuccessEvent(rememberMeAuth, this.getClass())); } if (this.successHandler != null) { this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth); @@ -141,7 +142,7 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements this.logger.debug(LogMessage .format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager " + "rejected Authentication returned by RememberMeServices: '%s'; " - + "invalidating remember-me token", rememberMeAuth), + + "invalidating remember-me token", remembermeToken), ex); this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, ex); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java index f9c72df806..e970e9326e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java @@ -23,6 +23,7 @@ import java.util.Date; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -194,7 +195,7 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { * Calculates the digital signature to be put in the cookie. * @since 5.8 */ - protected String makeTokenSignature(long tokenExpiryTime, String username, String password, + protected String makeTokenSignature(long tokenExpiryTime, String username, @Nullable String password, RememberMeTokenAlgorithm algorithm) { String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey(); try { @@ -273,15 +274,18 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { } protected String retrieveUserName(Authentication authentication) { - if (isInstanceOfUserDetails(authentication)) { - return ((UserDetails) authentication.getPrincipal()).getUsername(); + Object principal = authentication.getPrincipal(); + Assert.notNull(principal, "Authentication.getPrincipal() cannot be null"); + if (principal instanceof UserDetails userDetails) { + return userDetails.getUsername(); } - return authentication.getPrincipal().toString(); + return principal.toString(); } - protected String retrievePassword(Authentication authentication) { - if (isInstanceOfUserDetails(authentication)) { - return ((UserDetails) authentication.getPrincipal()).getPassword(); + protected @Nullable String retrievePassword(Authentication authentication) { + Object principal = authentication.getPrincipal(); + if (principal instanceof UserDetails userDetails) { + return userDetails.getPassword(); } if (authentication.getCredentials() != null) { return authentication.getCredentials().toString(); @@ -289,10 +293,6 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { return null; } - private boolean isInstanceOfUserDetails(Authentication authentication) { - return authentication.getPrincipal() instanceof UserDetails; - } - /** * Constant time comparison to prevent against timing attacks. */ @@ -302,7 +302,7 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { return MessageDigest.isEqual(expectedBytes, actualBytes); } - private static byte[] bytesUtf8(String s) { + private static byte @Nullable [] bytesUtf8(String s) { return (s != null) ? Utf8.encode(s) : null; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/package-info.java index 06d1e2cb7b..65aacfe904 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/package-info.java @@ -21,4 +21,7 @@ * "https://docs.spring.io/spring-security/site/docs/3.0.x/reference/remember-me.html">Remember-Me * Authentication chapter of the reference manual. */ +@NullMarked package org.springframework.security.web.authentication.rememberme; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java index 7c06ba95f6..4393aefb63 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java @@ -99,7 +99,9 @@ public class ConcurrentSessionControlAuthenticationStrategy // We permit unlimited logins return; } - List sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false); + Object principal = authentication.getPrincipal(); + Assert.notNull(principal, "Authentication.getPrincipal() cannot be null"); + List sessions = this.sessionRegistry.getAllSessions(principal, false); int sessionCount = sessions.size(); if (sessionCount < allowedSessions) { // They haven't got too many login sessions running at present diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/NullAuthenticatedSessionStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/NullAuthenticatedSessionStrategy.java index 83b99f5d79..55254fed7b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/NullAuthenticatedSessionStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/NullAuthenticatedSessionStrategy.java @@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.session; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -28,7 +29,7 @@ import org.springframework.security.core.Authentication; public final class NullAuthenticatedSessionStrategy implements SessionAuthenticationStrategy { @Override - public void onAuthentication(Authentication authentication, HttpServletRequest request, + public void onAuthentication(@Nullable Authentication authentication, HttpServletRequest request, HttpServletResponse response) { } diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/RegisterSessionAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/RegisterSessionAuthenticationStrategy.java index 65e727eebd..f7f50e3997 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/RegisterSessionAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/RegisterSessionAuthenticationStrategy.java @@ -64,7 +64,9 @@ public class RegisterSessionAuthenticationStrategy implements SessionAuthenticat @Override public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { - this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal()); + Object principal = authentication.getPrincipal(); + Assert.notNull(principal, "The principal cannot be null"); + this.sessionRegistry.registerNewSession(request.getSession().getId(), principal); } } diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/session/package-info.java index ad3a8da613..05a735e953 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/package-info.java @@ -24,4 +24,7 @@ *

  • Controlling the number of sessions an authenticated user can have open
  • * */ +@NullMarked package org.springframework.security.web.authentication.session; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java index 5b7af22bf3..e88273031e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/AuthenticationSwitchUserEvent.java @@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.switchuser; import java.io.Serial; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -32,19 +34,19 @@ public class AuthenticationSwitchUserEvent extends AbstractAuthenticationEvent { @Serial private static final long serialVersionUID = 6265996480231793939L; - private final UserDetails targetUser; + private final @Nullable UserDetails targetUser; /** * Switch user context event constructor * @param authentication The current Authentication object * @param targetUser The target user */ - public AuthenticationSwitchUserEvent(Authentication authentication, UserDetails targetUser) { + public AuthenticationSwitchUserEvent(Authentication authentication, @Nullable UserDetails targetUser) { super(authentication); this.targetUser = targetUser; } - public UserDetails getTargetUser() { + public @Nullable UserDetails getTargetUser() { return this.targetUser; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserAuthorityChanger.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserAuthorityChanger.java index 18a1b9db59..c0ca3f88bf 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserAuthorityChanger.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserAuthorityChanger.java @@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.switchuser; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -47,6 +49,7 @@ public interface SwitchUserAuthorityChanger { * @return the modified list of granted authorities. */ Collection modifyGrantedAuthorities(UserDetails targetUser, - Authentication currentAuthentication, Collection authoritiesToBeGranted); + @Nullable Authentication currentAuthentication, + Collection authoritiesToBeGranted); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java index ac85c23d59..2dc50d3fe2 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java @@ -27,6 +27,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationEventPublisher; @@ -122,7 +123,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - private ApplicationEventPublisher eventPublisher; + private @Nullable ApplicationEventPublisher eventPublisher; private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); @@ -132,22 +133,25 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv private RequestMatcher switchUserMatcher = createMatcher("/login/impersonate"); - private String targetUrl; + private @Nullable String targetUrl; - private String switchFailureUrl; + private @Nullable String switchFailureUrl; private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY; private String switchAuthorityRole = ROLE_PREVIOUS_ADMINISTRATOR; - private SwitchUserAuthorityChanger switchUserAuthorityChanger; + private @Nullable SwitchUserAuthorityChanger switchUserAuthorityChanger; + @SuppressWarnings("NullAway.Init") private UserDetailsService userDetailsService; private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); + @SuppressWarnings("NullAway.Init") private AuthenticationSuccessHandler successHandler; + @SuppressWarnings("NullAway.Init") private AuthenticationFailureHandler failureHandler; private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); @@ -239,8 +243,10 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv targetUserRequest = createSwitchUserToken(request, targetUser); // publish event if (this.eventPublisher != null) { - this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( - this.securityContextHolderStrategy.getContext().getAuthentication(), targetUser)); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); + if (authentication != null) { + this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(authentication, targetUser)); + } } return targetUserRequest; } @@ -316,7 +322,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv return targetUserRequest; } - private Authentication getCurrentAuthentication(HttpServletRequest request) { + private @Nullable Authentication getCurrentAuthentication(HttpServletRequest request) { try { // SEC-1763. Check first if we are already switched. return attemptExitUser(request); @@ -335,7 +341,7 @@ public class SwitchUserFilter extends GenericFilterBean implements ApplicationEv * @return The source user Authentication object or null * otherwise. */ - private Authentication getSourceAuthentication(Authentication current) { + private @Nullable Authentication getSourceAuthentication(Authentication current) { Authentication original = null; // iterate over granted authorities and find the 'switch user' authority Collection authorities = current.getAuthorities(); diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserGrantedAuthority.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserGrantedAuthority.java index 475beae7f9..153ace1fea 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserGrantedAuthority.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserGrantedAuthority.java @@ -16,6 +16,8 @@ package org.springframework.security.web.authentication.switchuser; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; @@ -38,7 +40,7 @@ public final class SwitchUserGrantedAuthority implements GrantedAuthority { private final Authentication source; - public SwitchUserGrantedAuthority(String role, Authentication source) { + public SwitchUserGrantedAuthority(String role, @Nullable Authentication source) { Assert.notNull(role, "role cannot be null"); Assert.notNull(source, "source cannot be null"); this.role = role; diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/package-info.java index 7723ec8b25..9105eeca91 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/package-info.java @@ -17,4 +17,7 @@ /** * Provides HTTP-based "switch user" (su) capabilities. */ +@NullMarked package org.springframework.security.web.authentication.switchuser; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java index 5b38f7ca38..3c74146b83 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java @@ -29,6 +29,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; @@ -51,11 +52,11 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { public static final String ERROR_PARAMETER_NAME = "error"; - private String loginPageUrl; + private @Nullable String loginPageUrl; - private String logoutSuccessUrl; + private @Nullable String logoutSuccessUrl; - private String failureUrl; + private @Nullable String failureUrl; private boolean formLoginEnabled; @@ -67,18 +68,20 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { private boolean oneTimeTokenEnabled; - private String authenticationUrl; + private @Nullable String authenticationUrl; - private String generateOneTimeTokenUrl; + private @Nullable String generateOneTimeTokenUrl; - private String usernameParameter; + private @Nullable String usernameParameter; - private String passwordParameter; + private @Nullable String passwordParameter; - private String rememberMeParameter; + private @Nullable String rememberMeParameter; + @SuppressWarnings("NullAway.Init") private Map oauth2AuthenticationUrlToClientName; + @SuppressWarnings("NullAway.Init") private Map saml2AuthenticationUrlToProviderName; private Function> resolveHiddenInputs = (request) -> Collections.emptyMap(); @@ -136,7 +139,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { this.logoutSuccessUrl = logoutSuccessUrl; } - public String getLoginPageUrl() { + public @Nullable String getLoginPageUrl() { return this.loginPageUrl; } @@ -361,7 +364,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { .render(); } - private String renderRememberMe(String paramName) { + private String renderRememberMe(@Nullable String paramName) { if (paramName == null) { return ""; } @@ -397,7 +400,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean { return "
    You have been signed out
    "; } - private boolean matches(HttpServletRequest request, String url) { + private boolean matches(HttpServletRequest request, @Nullable String url) { if (!"GET".equals(request.getMethod()) || url == null) { return false; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/ui/HtmlTemplates.java b/web/src/main/java/org/springframework/security/web/authentication/ui/HtmlTemplates.java index c90ea069fe..c10201a470 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ui/HtmlTemplates.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ui/HtmlTemplates.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.util.StringUtils; import org.springframework.web.util.HtmlUtils; @@ -57,8 +59,10 @@ final class HtmlTemplates { * @param value the value to inject * @return this instance for further templating */ - Builder withValue(String key, String value) { - this.values.put(key, HtmlUtils.htmlEscape(value)); + Builder withValue(String key, @Nullable String value) { + if (value != null) { + this.values.put(key, HtmlUtils.htmlEscape(value)); + } return this; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/ui/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/ui/package-info.java index 40db4e8515..779a60f196 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ui/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ui/package-info.java @@ -19,4 +19,7 @@ * appropriate login page when using namespace configuration without defining a login page * URL. */ +@NullMarked package org.springframework.security.web.authentication.ui; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java index 64dc3de205..6df7ca3d57 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.AuthenticationDetailsSource; @@ -75,7 +76,7 @@ public class BasicAuthenticationConverter implements AuthenticationConverter { } @Override - public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) { + public @Nullable UsernamePasswordAuthenticationToken convert(HttpServletRequest request) { String header = request.getHeader(HttpHeaders.AUTHORIZATION); if (header == null) { return null; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.java index 29658c8f56..fb81f0e591 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.java @@ -20,6 +20,7 @@ import java.io.IOException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpStatus; @@ -42,7 +43,7 @@ import org.springframework.util.Assert; */ public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { - private String realmName; + private @Nullable String realmName; @Override public void afterPropertiesSet() { @@ -56,7 +57,7 @@ public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint, response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } - public String getRealmName() { + public @Nullable String getRealmName() { return this.realmName; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index cf4e2ab5b1..98fdd58da1 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -23,6 +23,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -96,7 +97,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); - private AuthenticationEntryPoint authenticationEntryPoint; + private @Nullable AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationManager authenticationManager; @@ -201,7 +202,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { this.logger.debug("Failed to process authentication request", ex); this.rememberMeServices.loginFail(request, response); onUnsuccessfulAuthentication(request, response, ex); - if (this.ignoreFailure) { + if (this.ignoreFailure || this.authenticationEntryPoint == null) { chain.doFilter(request, response); } else { @@ -241,7 +242,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { AuthenticationException failed) throws IOException { } - protected AuthenticationEntryPoint getAuthenticationEntryPoint() { + protected @Nullable AuthenticationEntryPoint getAuthenticationEntryPoint() { return this.authenticationEntryPoint; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java index 9951473d88..d29a98d8ed 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java @@ -23,6 +23,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.codec.Hex; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -34,13 +36,14 @@ final class DigestAuthUtils { private DigestAuthUtils() { } - static String encodePasswordInA1Format(String username, String realm, String password) { + static String encodePasswordInA1Format(@Nullable String username, @Nullable String realm, + @Nullable String password) { String a1 = username + ":" + realm + ":" + password; return md5Hex(a1); } - static String[] splitIgnoringQuotes(String str, char separatorChar) { + static String @Nullable [] splitIgnoringQuotes(String str, char separatorChar) { if (str == null) { return null; } @@ -100,9 +103,9 @@ final class DigestAuthUtils { * @return the MD5 of the digest authentication response, encoded in hex * @throws IllegalArgumentException if the supplied qop value is unsupported. */ - static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password, - String httpMethod, String uri, String qop, String nonce, String nc, String cnonce) - throws IllegalArgumentException { + static String generateDigest(boolean passwordAlreadyEncoded, @Nullable String username, @Nullable String realm, + @Nullable String password, String httpMethod, @Nullable String uri, @Nullable String qop, + @Nullable String nonce, @Nullable String nc, @Nullable String cnonce) throws IllegalArgumentException { String a2 = httpMethod + ":" + uri; String a1Md5 = (!passwordAlreadyEncoded) ? DigestAuthUtils.encodePasswordInA1Format(username, realm, password) : password; @@ -134,7 +137,7 @@ final class DigestAuthUtils { * @return a Map representing the array contents, or null if * the array to process was null or empty */ - static Map splitEachArrayElementAndCreateMap(String[] array, String delimiter, + static @Nullable Map splitEachArrayElementAndCreateMap(String @Nullable [] array, String delimiter, String removeCharacters) { if ((array == null) || (array.length == 0)) { return null; @@ -162,7 +165,7 @@ final class DigestAuthUtils { * being after the delimiter (neither element includes the delimiter) * @throws IllegalArgumentException if an argument was invalid */ - static String[] split(String toSplit, String delimiter) { + static String @Nullable [] split(String toSplit, String delimiter) { Assert.hasLength(toSplit, "Cannot split a null or empty string"); Assert.hasLength(delimiter, "Cannot use a null or empty delimiter to split a string"); Assert.isTrue(delimiter.length() == 1, "Delimiter can only be one character in length"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java index f33526c525..e665f1726b 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java @@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Ordered; @@ -50,9 +51,9 @@ public class DigestAuthenticationEntryPoint implements AuthenticationEntryPoint, private static final Log logger = LogFactory.getLog(DigestAuthenticationEntryPoint.class); - private String key; + private @Nullable String key; - private String realmName; + private @Nullable String realmName; private int nonceValiditySeconds = 300; @@ -95,7 +96,7 @@ public class DigestAuthenticationEntryPoint implements AuthenticationEntryPoint, response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } - public String getKey() { + public @Nullable String getKey() { return this.key; } @@ -103,7 +104,7 @@ public class DigestAuthenticationEntryPoint implements AuthenticationEntryPoint, return this.nonceValiditySeconds; } - public String getRealmName() { + public @Nullable String getRealmName() { return this.realmName; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java index bf23561280..24c9d02f54 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java @@ -18,7 +18,9 @@ package org.springframework.security.web.authentication.www; import java.io.IOException; import java.util.Base64; +import java.util.Collections; import java.util.Map; +import java.util.Objects; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -28,6 +30,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; @@ -99,12 +102,14 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); + @SuppressWarnings("NullAway.Init") private DigestAuthenticationEntryPoint authenticationEntryPoint; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private UserCache userCache = new NullUserCache(); + @SuppressWarnings("NullAway.Init") private UserDetailsService userDetailsService; private boolean passwordAlreadyEncoded = false; @@ -146,12 +151,14 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes // clear text - not encoded/salted (unless this instance's passwordAlreadyEncoded // property is 'false') boolean cacheWasUsed = true; - UserDetails user = this.userCache.getUserFromCache(digestAuth.getUsername()); + // username was validated as non-null in digestAuth validateAndDecode + String username = Objects.requireNonNull(digestAuth.getUsername()); + UserDetails user = this.userCache.getUserFromCache(username); String serverDigestMd5; try { if (user == null) { cacheWasUsed = false; - user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername()); + user = this.userDetailsService.loadUserByUsername(username); if (user == null) { throw new AuthenticationServiceException( "AuthenticationDao returned null, which is an interface contract violation"); @@ -162,14 +169,14 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes // If digest is incorrect, try refreshing from backend and recomputing if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) { logger.debug("Digest comparison failure; trying to refresh user from DAO in case password had changed"); - user = this.userDetailsService.loadUserByUsername(digestAuth.getUsername()); + user = this.userDetailsService.loadUserByUsername(username); this.userCache.putUserInCache(user); serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod()); } } catch (UsernameNotFoundException ex) { String message = this.messages.getMessage("DigestAuthenticationFilter.usernameNotFound", - new Object[] { digestAuth.getUsername() }, "Username {0} not found"); + new Object[] { username }, "Username {0} not found"); fail(request, response, new BadCredentialsException(message)); return; } @@ -193,8 +200,8 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes fail(request, response, new NonceExpiredException(message)); return; } - logger.debug(LogMessage.format("Authentication success for user: '%s' with response: '%s'", - digestAuth.getUsername(), digestAuth.getResponse())); + logger.debug(LogMessage.format("Authentication success for user: '%s' with response: '%s'", username, + digestAuth.getResponse())); Authentication authentication = createSuccessfulAuthentication(request, user); SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); @@ -232,7 +239,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes return this.userCache; } - public UserDetailsService getUserDetailsService() { + public @Nullable UserDetailsService getUserDetailsService() { return this.userDetailsService; } @@ -304,21 +311,21 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes private class DigestData { - private final String username; + private final @Nullable String username; - private final String realm; + private final @Nullable String realm; - private final String nonce; + private final @Nullable String nonce; - private final String uri; + private final @Nullable String uri; - private final String response; + private final @Nullable String response; - private final String qop; + private final @Nullable String qop; - private final String nc; + private final @Nullable String nc; - private final String cnonce; + private final @Nullable String cnonce; private final String section212response; @@ -328,6 +335,9 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes this.section212response = header.substring(7); String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(this.section212response, ','); Map headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\""); + if (headerMap == null) { + headerMap = Collections.emptyMap(); + } this.username = headerMap.get("username"); this.realm = headerMap.get("realm"); this.nonce = headerMap.get("nonce"); @@ -341,7 +351,8 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes this.username, this.realm, this.nonce, this.uri, this.response)); } - void validateAndDecode(String entryPointKey, String expectedRealm) throws BadCredentialsException { + void validateAndDecode(@Nullable String entryPointKey, @Nullable String expectedRealm) + throws BadCredentialsException { // Check all required parameters were supplied (ie RFC 2069) if ((this.username == null) || (this.realm == null) || (this.nonce == null) || (this.uri == null) || (this.response == null)) { @@ -359,7 +370,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes } } // Check realm name equals what we expected - if (!expectedRealm.equals(this.realm)) { + if (!this.realm.equals(expectedRealm)) { throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage( "DigestAuthenticationFilter.incorrectRealm", new Object[] { this.realm, expectedRealm }, "Response realm name '{0}' does not match system realm name of '{1}'")); @@ -401,7 +412,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes } } - String calculateServerDigest(String password, String httpMethod) { + String calculateServerDigest(@Nullable String password, String httpMethod) { // Compute the expected response-digest (will be in hex form). Don't catch // IllegalArgumentException (already checked validity) return DigestAuthUtils.generateDigest(DigestAuthenticationFilter.this.passwordAlreadyEncoded, this.username, @@ -413,11 +424,11 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes return this.nonceExpiryTime < now; } - String getUsername() { + @Nullable String getUsername() { return this.username; } - String getResponse() { + @Nullable String getResponse() { return this.response; } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/package-info.java b/web/src/main/java/org/springframework/security/web/authentication/www/package-info.java index 7ab36c91cc..d015eabe8e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/package-info.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/package-info.java @@ -18,4 +18,7 @@ * WWW-Authenticate based authentication mechanism implementations: Basic and Digest * authentication. */ +@NullMarked package org.springframework.security.web.authentication.www; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/bind/annotation/package-info.java b/web/src/main/java/org/springframework/security/web/bind/annotation/package-info.java new file mode 100644 index 0000000000..a10f8669dd --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/bind/annotation/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. + */ + +/** + * Annotations for binding web security APIs. + */ +@NullMarked +package org.springframework.security.web.bind.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/bind/package-info.java b/web/src/main/java/org/springframework/security/web/bind/package-info.java new file mode 100644 index 0000000000..3a262c9422 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/bind/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. + */ + +/** + * APIs for binding web security APIs. + */ +@NullMarked +package org.springframework.security.web.bind; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java index f7dfcd3889..22dd61028d 100644 --- a/web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/bind/support/AuthenticationPrincipalArgumentResolver.java @@ -18,6 +18,8 @@ package org.springframework.security.web.bind.support; import java.lang.annotation.Annotation; +import org.jspecify.annotations.Nullable; + import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.core.Authentication; @@ -93,16 +95,16 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet } @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return null; } Object principal = authentication.getPrincipal(); if (principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass())) { - AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter); - if (authPrincipal.errorOnInvalidType()) { + @Nullable AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter); + if (authPrincipal != null && authPrincipal.errorOnInvalidType()) { throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType()); } return null; @@ -117,7 +119,8 @@ 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 T findMethodAnnotation(Class annotationClass, MethodParameter parameter) { + private @Nullable T findMethodAnnotation(Class annotationClass, + MethodParameter parameter) { T annotation = parameter.getParameterAnnotation(annotationClass); if (annotation != null) { return annotation; diff --git a/web/src/main/java/org/springframework/security/web/bind/support/package-info.java b/web/src/main/java/org/springframework/security/web/bind/support/package-info.java new file mode 100644 index 0000000000..c24d9d8738 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/bind/support/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 binding web security APIs. + */ +@NullMarked +package org.springframework.security.web.bind.support; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java b/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java index dc15b11abd..ee13902712 100644 --- a/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java +++ b/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java @@ -25,6 +25,7 @@ import jakarta.servlet.Filter; import jakarta.servlet.FilterRegistration.Dynamic; import jakarta.servlet.ServletContext; import jakarta.servlet.SessionTrackingMode; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.core.Conventions; @@ -77,7 +78,7 @@ public abstract class AbstractSecurityWebApplicationInitializer implements WebAp public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; - private final Class[] configurationClasses; + private final Class @Nullable [] configurationClasses; /** * Creates a new instance that assumes the Spring Security configuration is loaded by @@ -212,7 +213,7 @@ public abstract class AbstractSecurityWebApplicationInitializer implements WebAp * @return the {@link DelegatingFilterProxy#getContextAttribute()} or null if the * parent {@link ApplicationContext} should be used */ - private String getWebApplicationContextAttribute() { + private @Nullable String getWebApplicationContextAttribute() { String dispatcherServletName = getDispatcherWebApplicationContextSuffix(); if (dispatcherServletName == null) { return null; @@ -255,7 +256,7 @@ public abstract class AbstractSecurityWebApplicationInitializer implements WebAp * {@link WebApplicationContext} or null (default) to use the parent * {@link ApplicationContext}. */ - protected String getDispatcherWebApplicationContextSuffix() { + protected @Nullable String getDispatcherWebApplicationContextSuffix() { return null; } diff --git a/web/src/main/java/org/springframework/security/web/context/DelegatingSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/DelegatingSecurityContextRepository.java index 51f792dfd6..8aa8e6c96d 100644 --- a/web/src/main/java/org/springframework/security/web/context/DelegatingSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/DelegatingSecurityContextRepository.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.context.DeferredSecurityContext; import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; /** @@ -51,7 +52,7 @@ public final class DelegatingSecurityContextRepository implements SecurityContex @Override @Deprecated public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { - SecurityContext result = null; + SecurityContext result = SecurityContextHolder.createEmptyContext(); for (SecurityContextRepository delegate : this.delegates) { SecurityContext delegateResult = delegate.loadContext(requestResponseHolder); if (result == null || delegate.containsContext(requestResponseHolder.getRequest())) { @@ -73,6 +74,9 @@ public final class DelegatingSecurityContextRepository implements SecurityContex deferredSecurityContext = new DelegatingDeferredSecurityContext(deferredSecurityContext, next); } } + if (deferredSecurityContext == null) { + throw new IllegalStateException("No deferredSecurityContext found"); + } return deferredSecurityContext; } diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java index 36796ead5c..d77192feec 100644 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java @@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.log.LogMessage; @@ -203,7 +204,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo /** * @param httpSession the session obtained from the request. */ - private SecurityContext readSecurityContextFromSession(HttpSession httpSession) { + private @Nullable SecurityContext readSecurityContextFromSession(HttpSession httpSession) { if (httpSession == null) { this.logger.trace("No HttpSession currently exists"); return null; @@ -295,7 +296,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo this.contextObject = this.securityContextHolderStrategy.createEmptyContext(); } - private boolean isTransient(Object object) { + private boolean isTransient(@Nullable Object object) { if (object == null) { return false; } @@ -355,7 +356,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo private final SecurityContext contextBeforeExecution; - private final Authentication authBeforeExecution; + private final @Nullable Authentication authBeforeExecution; private boolean isSaveContextInvoked; @@ -438,7 +439,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo || context.getAuthentication() != this.authBeforeExecution; } - private HttpSession createNewSessionIfAllowed(SecurityContext context) { + private @Nullable HttpSession createNewSessionIfAllowed(SecurityContext context) { if (this.httpSessionExistedAtStartOfRequest) { this.logger.debug("HttpSession is now null, but was not null at start of request; " + "session was invalidated, so do not create a new session"); diff --git a/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java index 2d22591c1b..21fbfab363 100644 --- a/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/NullSecurityContextRepository.java @@ -18,6 +18,7 @@ package org.springframework.security.web.context; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -48,7 +49,8 @@ public final class NullSecurityContextRepository implements SecurityContextRepos } @Override - public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { + public void saveContext(SecurityContext context, HttpServletRequest request, + @Nullable HttpServletResponse response) { } /** diff --git a/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java index 745cbb875e..d4bb44810d 100644 --- a/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/RequestAttributeSecurityContextRepository.java @@ -20,6 +20,7 @@ import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.context.DeferredSecurityContext; import org.springframework.security.core.context.SecurityContext; @@ -95,7 +96,8 @@ public final class RequestAttributeSecurityContextRepository implements Security } @Override - public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { + public void saveContext(SecurityContext context, HttpServletRequest request, + @Nullable HttpServletResponse response) { request.setAttribute(this.requestAttributeName, context); } diff --git a/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java index 520d3f4aff..674d79de8a 100644 --- a/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/SecurityContextRepository.java @@ -78,7 +78,11 @@ public interface SecurityContextRepository { * @since 5.8 */ default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) { - Supplier supplier = () -> loadContext(new HttpRequestResponseHolder(request, null)); + Supplier supplier = () -> { + @SuppressWarnings("NullAway") // fixed when remove deprecated method + HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, null); + return loadContext(holder); + }; return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier), SecurityContextHolder.getContextHolderStrategy()); } diff --git a/web/src/main/java/org/springframework/security/web/context/SupplierDeferredSecurityContext.java b/web/src/main/java/org/springframework/security/web/context/SupplierDeferredSecurityContext.java index d255f6f845..1ff20f5b95 100644 --- a/web/src/main/java/org/springframework/security/web/context/SupplierDeferredSecurityContext.java +++ b/web/src/main/java/org/springframework/security/web/context/SupplierDeferredSecurityContext.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.security.core.context.DeferredSecurityContext; @@ -38,7 +39,7 @@ final class SupplierDeferredSecurityContext implements DeferredSecurityContext { private final SecurityContextHolderStrategy strategy; - private SecurityContext securityContext; + private @Nullable SecurityContext securityContext; private boolean missingContext; @@ -48,7 +49,7 @@ final class SupplierDeferredSecurityContext implements DeferredSecurityContext { } @Override - public SecurityContext get() { + public @Nullable SecurityContext get() { init(); return this.securityContext; } diff --git a/web/src/main/java/org/springframework/security/web/context/package-info.java b/web/src/main/java/org/springframework/security/web/context/package-info.java index a4845ed8a1..eb1b3a9b5a 100644 --- a/web/src/main/java/org/springframework/security/web/context/package-info.java +++ b/web/src/main/java/org/springframework/security/web/context/package-info.java @@ -18,4 +18,7 @@ * Classes which are responsible for maintaining the security context between HTTP * requests. */ +@NullMarked package org.springframework.security.web.context; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java b/web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java index 3c8ed08044..ba0527f71d 100644 --- a/web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java +++ b/web/src/main/java/org/springframework/security/web/context/request/async/SecurityContextCallableProcessingInterceptor.java @@ -18,6 +18,8 @@ package org.springframework.security.web.context.request.async; import java.util.concurrent.Callable; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -42,7 +44,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept */ public final class SecurityContextCallableProcessingInterceptor implements CallableProcessingInterceptor { - private volatile SecurityContext securityContext; + private @Nullable volatile SecurityContext securityContext; private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); @@ -77,11 +79,13 @@ public final class SecurityContextCallableProcessingInterceptor implements Calla @Override public void preProcess(NativeWebRequest request, Callable task) { - this.securityContextHolderStrategy.setContext(this.securityContext); + if (this.securityContext != null) { + this.securityContextHolderStrategy.setContext(this.securityContext); + } } @Override - public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + public void postProcess(NativeWebRequest request, Callable task, @Nullable Object concurrentResult) { this.securityContextHolderStrategy.clearContext(); } diff --git a/web/src/main/java/org/springframework/security/web/context/request/async/package-info.java b/web/src/main/java/org/springframework/security/web/context/request/async/package-info.java new file mode 100644 index 0000000000..d01dc34f2e --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/context/request/async/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. + */ + +/** + * Async request context APIs. + */ +@NullMarked +package org.springframework.security.web.context.request.async; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/context/support/package-info.java b/web/src/main/java/org/springframework/security/web/context/support/package-info.java new file mode 100644 index 0000000000..57a9387fdf --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/context/support/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. + */ + +/** + * Async support for request context. + */ +@NullMarked +package org.springframework.security.web.context.support; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java index 19df354824..027798f60a 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CookieCsrfTokenRepository.java @@ -22,6 +22,7 @@ import java.util.function.Consumer; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; @@ -58,11 +59,11 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { private boolean cookieHttpOnly = true; - private String cookiePath; + private @Nullable String cookiePath; - private String cookieDomain; + private @Nullable String cookieDomain; - private Boolean secure; + private @Nullable Boolean secure; private int cookieMaxAge = -1; @@ -86,7 +87,7 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { } @Override - public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { + public void saveToken(@Nullable CsrfToken token, HttpServletRequest request, HttpServletResponse response) { String tokenValue = (token != null) ? token.getToken() : ""; ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from(this.cookieName, tokenValue) @@ -122,7 +123,7 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { } @Override - public CsrfToken loadToken(HttpServletRequest request) { + public @Nullable CsrfToken loadToken(HttpServletRequest request) { // Return null when token has been removed during the current request // which allows loadDeferredToken to re-generate the token if (Boolean.TRUE.equals(request.getAttribute(CSRF_TOKEN_REMOVED_ATTRIBUTE_NAME))) { @@ -218,7 +219,7 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository { * Get the path that the CSRF cookie will be set to. * @return the path to be used. */ - public String getCookiePath() { + public @Nullable String getCookiePath() { return this.cookiePath; } diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java index 201775ba8d..de1705b92d 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java @@ -20,6 +20,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.session.SessionAuthenticationException; @@ -63,7 +64,7 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt } @Override - public void onAuthentication(Authentication authentication, HttpServletRequest request, + public void onAuthentication(@Nullable Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { boolean containsToken = this.tokenRepository.loadToken(request) != null; if (containsToken) { diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java index 462797728c..20912ab8c0 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java @@ -28,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; 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.security.access.AccessDeniedException; @@ -190,7 +191,7 @@ public final class CsrfFilter extends OncePerRequestFilter { * @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/web/src/main/java/org/springframework/security/web/csrf/CsrfLogoutHandler.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfLogoutHandler.java index a84dc695b4..0cf20b6b91 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfLogoutHandler.java @@ -18,6 +18,7 @@ package org.springframework.security.web.csrf; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutHandler; @@ -51,7 +52,8 @@ public final class CsrfLogoutHandler implements LogoutHandler { * org.springframework.security.core.Authentication) */ @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + public void logout(HttpServletRequest request, HttpServletResponse response, + @Nullable Authentication authentication) { this.csrfTokenRepository.saveToken(null, request, response); } diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRepository.java index d7c0cc2e6e..c0fa366338 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRepository.java @@ -19,6 +19,7 @@ package org.springframework.security.web.csrf; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; /** * An API to allow changing the method in which the expected {@link CsrfToken} is @@ -47,14 +48,14 @@ public interface CsrfTokenRepository { * @param request the {@link HttpServletRequest} to use * @param response the {@link HttpServletResponse} to use */ - void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response); + void saveToken(@Nullable CsrfToken token, HttpServletRequest request, HttpServletResponse response); /** * Loads the expected {@link CsrfToken} from the {@link HttpServletRequest} * @param request the {@link HttpServletRequest} to use * @return the {@link CsrfToken} or null if none exists */ - CsrfToken loadToken(HttpServletRequest request); + @Nullable CsrfToken loadToken(HttpServletRequest request); /** * Defers loading the {@link CsrfToken} using the {@link HttpServletRequest} and diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java index 84f04f7021..b0cc63af47 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestHandler.java @@ -20,6 +20,7 @@ import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; @@ -47,7 +48,7 @@ public interface CsrfTokenRequestHandler extends CsrfTokenRequestResolver { void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken); @Override - default String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { + default @Nullable String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { Assert.notNull(request, "request cannot be null"); Assert.notNull(csrfToken, "csrfToken cannot be null"); String actualToken = request.getHeader(csrfToken.getHeaderName()); diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java index 359454bc01..fe7f67dd4c 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestResolver.java @@ -17,6 +17,7 @@ package org.springframework.security.web.csrf; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; /** * Implementations of this interface are capable of resolving the token value of a @@ -37,6 +38,6 @@ public interface CsrfTokenRequestResolver { * @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository} * @return the token value resolved from the request */ - String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken); + @Nullable String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken); } diff --git a/web/src/main/java/org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.java index 0a6a7f4e5d..834c455b5c 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.java @@ -21,6 +21,7 @@ import java.util.UUID; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -47,7 +48,7 @@ public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME; @Override - public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { + public void saveToken(@Nullable CsrfToken token, HttpServletRequest request, HttpServletResponse response) { if (token == null) { HttpSession session = request.getSession(false); if (session != null) { @@ -61,7 +62,7 @@ public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository } @Override - public CsrfToken loadToken(HttpServletRequest request) { + public @Nullable CsrfToken loadToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; diff --git a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java index 68c3dd77f2..46c8089ef6 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java +++ b/web/src/main/java/org/springframework/security/web/csrf/InvalidCsrfTokenException.java @@ -19,6 +19,7 @@ package org.springframework.security.web.csrf; import java.io.Serial; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; /** * Thrown when an expected {@link CsrfToken} exists, but it does not match the value @@ -36,7 +37,7 @@ public class InvalidCsrfTokenException extends CsrfException { * @param expectedAccessToken * @param actualAccessToken */ - public InvalidCsrfTokenException(CsrfToken expectedAccessToken, String actualAccessToken) { + public InvalidCsrfTokenException(CsrfToken expectedAccessToken, @Nullable String actualAccessToken) { super("Invalid CSRF Token '" + actualAccessToken + "' was found on the request parameter '" + expectedAccessToken.getParameterName() + "' or header '" + expectedAccessToken.getHeaderName() + "'."); diff --git a/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java index af04144cc0..c7a785fdd1 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java +++ b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java @@ -16,6 +16,8 @@ package org.springframework.security.web.csrf; +import org.jspecify.annotations.Nullable; + /** * Thrown when no expected {@link CsrfToken} is found but is required. * @@ -25,7 +27,7 @@ package org.springframework.security.web.csrf; @SuppressWarnings("serial") public class MissingCsrfTokenException extends CsrfException { - public MissingCsrfTokenException(String actualToken) { + public MissingCsrfTokenException(@Nullable String actualToken) { super("Could not verify the provided CSRF token because no token was found to compare."); } diff --git a/web/src/main/java/org/springframework/security/web/csrf/RepositoryDeferredCsrfToken.java b/web/src/main/java/org/springframework/security/web/csrf/RepositoryDeferredCsrfToken.java index d7d437848c..2fdd1be50d 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/RepositoryDeferredCsrfToken.java +++ b/web/src/main/java/org/springframework/security/web/csrf/RepositoryDeferredCsrfToken.java @@ -16,8 +16,11 @@ package org.springframework.security.web.csrf; +import java.util.Objects; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; /** * @author Rob Winch @@ -32,7 +35,7 @@ final class RepositoryDeferredCsrfToken implements DeferredCsrfToken { private final HttpServletResponse response; - private CsrfToken csrfToken; + private @Nullable CsrfToken csrfToken; private boolean missingToken; @@ -46,7 +49,7 @@ final class RepositoryDeferredCsrfToken implements DeferredCsrfToken { @Override public CsrfToken get() { init(); - return this.csrfToken; + return Objects.requireNonNull(this.csrfToken); } @Override diff --git a/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java index 96c6f00363..ad1220ca57 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestAttributeHandler.java @@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.security.crypto.codec.Utf8; @@ -74,7 +75,7 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA } @Override - public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { + public @Nullable String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) { String actualToken = super.resolveCsrfTokenValue(request, csrfToken); if (actualToken == null) { return null; @@ -82,7 +83,7 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA return getTokenValue(actualToken, csrfToken.getToken()); } - private static String getTokenValue(String actualToken, String token) { + private static @Nullable String getTokenValue(String actualToken, String token) { byte[] actualBytes; try { actualBytes = Base64.getUrlDecoder().decode(actualToken); @@ -140,7 +141,7 @@ public final class XorCsrfTokenRequestAttributeHandler extends CsrfTokenRequestA private final Supplier delegate; - private CsrfToken csrfToken; + private @Nullable CsrfToken csrfToken; private CachedCsrfTokenSupplier(Supplier delegate) { this.delegate = delegate; diff --git a/web/src/main/java/org/springframework/security/web/csrf/package-info.java b/web/src/main/java/org/springframework/security/web/csrf/package-info.java new file mode 100644 index 0000000000..82be50af9e --- /dev/null +++ b/web/src/main/java/org/springframework/security/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. + */ + +/** + * APIs for protection against CSRF attacks. + */ +@NullMarked +package org.springframework.security.web.csrf; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/debug/DebugFilter.java b/web/src/main/java/org/springframework/security/web/debug/DebugFilter.java index d1cfab6692..99da9ad172 100644 --- a/web/src/main/java/org/springframework/security/web/debug/DebugFilter.java +++ b/web/src/main/java/org/springframework/security/web/debug/DebugFilter.java @@ -30,6 +30,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; @@ -113,7 +114,7 @@ public final class DebugFilter implements Filter { return sb.toString(); } - String formatFilters(List filters) { + String formatFilters(@Nullable List filters) { StringBuilder sb = new StringBuilder(); sb.append("Security filter chain: "); if (filters == null) { @@ -133,7 +134,7 @@ public final class DebugFilter implements Filter { return sb.toString(); } - private List getFilters(HttpServletRequest request) { + private @Nullable List getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : this.filterChainProxy.getFilterChains()) { if (chain.matches(request)) { return chain.getFilters(); diff --git a/web/src/main/java/org/springframework/security/web/debug/package-info.java b/web/src/main/java/org/springframework/security/web/debug/package-info.java new file mode 100644 index 0000000000..e8675d679a --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/debug/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. + */ + +/** + * APIs for debugging web security. + */ +@NullMarked +package org.springframework.security.web.debug; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/firewall/RequestWrapper.java b/web/src/main/java/org/springframework/security/web/firewall/RequestWrapper.java index 1ae04a804b..59cbab7559 100644 --- a/web/src/main/java/org/springframework/security/web/firewall/RequestWrapper.java +++ b/web/src/main/java/org/springframework/security/web/firewall/RequestWrapper.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; /** * Request wrapper which ensures values of {@code servletPath} and {@code pathInfo} are @@ -46,9 +47,9 @@ import jakarta.servlet.http.HttpServletRequest; */ final class RequestWrapper extends FirewalledRequest { - private final String strippedServletPath; + private final @Nullable String strippedServletPath; - private final String strippedPathInfo; + private final @Nullable String strippedPathInfo; private boolean stripPaths = true; @@ -70,7 +71,7 @@ final class RequestWrapper extends FirewalledRequest { * @return the supplied value, with path parameters removed and sequences of multiple * '/' characters truncated, or null if the supplied path was null. */ - private String strip(String path) { + private @Nullable String strip(String path) { if (path == null) { return null; } @@ -104,12 +105,12 @@ final class RequestWrapper extends FirewalledRequest { } @Override - public String getPathInfo() { + public @Nullable String getPathInfo() { return this.stripPaths ? this.strippedPathInfo : super.getPathInfo(); } @Override - public String getServletPath() { + public @Nullable String getServletPath() { return this.stripPaths ? this.strippedServletPath : super.getServletPath(); } diff --git a/web/src/main/java/org/springframework/security/web/firewall/package-info.java b/web/src/main/java/org/springframework/security/web/firewall/package-info.java new file mode 100644 index 0000000000..ac3cce0eb5 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/firewall/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. + */ + +/** + * APIs for web security firewall support. + */ +@NullMarked +package org.springframework.security.web.firewall; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/header/package-info.java b/web/src/main/java/org/springframework/security/web/header/package-info.java new file mode 100644 index 0000000000..a325f34178 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/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. + */ + +/** + * APIs for writing security HTTP Headers. + */ +@NullMarked +package org.springframework.security.web.header; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java index 7ba7b0d9b9..768ff0f272 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -35,7 +36,7 @@ public final class CrossOriginEmbedderPolicyHeaderWriter implements HeaderWriter private static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; - private CrossOriginEmbedderPolicy policy; + private @Nullable CrossOriginEmbedderPolicy policy; /** * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the @@ -72,7 +73,7 @@ public final class CrossOriginEmbedderPolicyHeaderWriter implements HeaderWriter return this.policy; } - public static CrossOriginEmbedderPolicy from(String embedderPolicy) { + public static @Nullable CrossOriginEmbedderPolicy from(String embedderPolicy) { for (CrossOriginEmbedderPolicy policy : values()) { if (policy.getPolicy().equals(embedderPolicy)) { return policy; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java index 5162ab325b..7087c52fbc 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -35,7 +36,7 @@ public final class CrossOriginOpenerPolicyHeaderWriter implements HeaderWriter { private static final String OPENER_POLICY = "Cross-Origin-Opener-Policy"; - private CrossOriginOpenerPolicy policy; + private @Nullable CrossOriginOpenerPolicy policy; /** * Sets the {@link CrossOriginOpenerPolicy} value to be used in the @@ -72,7 +73,7 @@ public final class CrossOriginOpenerPolicyHeaderWriter implements HeaderWriter { return this.policy; } - public static CrossOriginOpenerPolicy from(String openerPolicy) { + public static @Nullable CrossOriginOpenerPolicy from(String openerPolicy) { for (CrossOriginOpenerPolicy policy : values()) { if (policy.getPolicy().equals(openerPolicy)) { return policy; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java index 375727af6e..4d7df47eda 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -35,7 +36,7 @@ public final class CrossOriginResourcePolicyHeaderWriter implements HeaderWriter private static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; - private CrossOriginResourcePolicy policy; + private @Nullable CrossOriginResourcePolicy policy; /** * Sets the {@link CrossOriginResourcePolicy} value to be used in the @@ -72,7 +73,7 @@ public final class CrossOriginResourcePolicyHeaderWriter implements HeaderWriter return this.policy; } - public static CrossOriginResourcePolicy from(String resourcePolicy) { + public static @Nullable CrossOriginResourcePolicy from(String resourcePolicy) { for (CrossOriginResourcePolicy policy : values()) { if (policy.getPolicy().equals(resourcePolicy)) { return policy; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java index 9cd1134427..a6dab3bdb4 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -39,7 +40,7 @@ public final class PermissionsPolicyHeaderWriter implements HeaderWriter { private static final String PERMISSIONS_POLICY_HEADER = "Permissions-Policy"; - private String policy; + private @Nullable String policy; /** * Create a new instance of {@link PermissionsPolicyHeaderWriter}. diff --git a/web/src/main/java/org/springframework/security/web/header/writers/ReferrerPolicyHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/ReferrerPolicyHeaderWriter.java index aeeea71b76..750d0378fc 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/ReferrerPolicyHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/ReferrerPolicyHeaderWriter.java @@ -22,6 +22,7 @@ import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -74,7 +75,8 @@ public class ReferrerPolicyHeaderWriter implements HeaderWriter { * @throws IllegalArgumentException if policy is null */ public ReferrerPolicyHeaderWriter(ReferrerPolicy policy) { - setPolicy(policy); + Assert.notNull(policy, "policy can not be null"); + this.policy = policy; } /** @@ -136,7 +138,7 @@ public class ReferrerPolicyHeaderWriter implements HeaderWriter { return this.policy; } - public static ReferrerPolicy get(String referrerPolicy) { + public static @Nullable ReferrerPolicy get(String referrerPolicy) { return REFERRER_POLICIES.get(referrerPolicy); } diff --git a/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java index 0e9ce14ef6..cb54702b91 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/XXssProtectionHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -101,7 +102,7 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter { this.value = value; } - public static HeaderValue from(String headerValue) { + public static @Nullable HeaderValue from(String headerValue) { for (HeaderValue value : values()) { if (value.toString().equals(headerValue)) { return value; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java index 5836254ef4..b22412f0c4 100644 --- a/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/XFrameOptionsHeaderWriter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.header.writers.frameoptions; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.header.HeaderWriter; import org.springframework.util.Assert; @@ -35,7 +36,7 @@ public final class XFrameOptionsHeaderWriter implements HeaderWriter { public static final String XFRAME_OPTIONS_HEADER = "X-Frame-Options"; - private final AllowFromStrategy allowFromStrategy; + private final @Nullable AllowFromStrategy allowFromStrategy; private final XFrameOptionsMode frameOptionsMode; @@ -87,6 +88,8 @@ public final class XFrameOptionsHeaderWriter implements HeaderWriter { @Override public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { if (XFrameOptionsMode.ALLOW_FROM.equals(this.frameOptionsMode)) { + Assert.notNull(this.allowFromStrategy, + "AllowFromStrategy cannot be null when frameOptionsMode is ALLOW_FROM"); String allowFromValue = this.allowFromStrategy.getAllowFromValue(request); if (XFrameOptionsMode.DENY.getMode().equals(allowFromValue)) { if (!response.containsHeader(XFRAME_OPTIONS_HEADER)) { diff --git a/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/package-info.java b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/package-info.java new file mode 100644 index 0000000000..9d1d9c3ae0 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/frameoptions/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. + */ + +/** + * APIs for writing security HTTP Headers related to frame options. + */ +@NullMarked +package org.springframework.security.web.header.writers.frameoptions; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/header/writers/package-info.java b/web/src/main/java/org/springframework/security/web/header/writers/package-info.java new file mode 100644 index 0000000000..a6a6289519 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/header/writers/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. + */ + +/** + * APIs for writing security HTTP Headers. + */ +@NullMarked +package org.springframework.security.web.header.writers; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/http/package-info.java b/web/src/main/java/org/springframework/security/web/http/package-info.java new file mode 100644 index 0000000000..bd7b04a9e4 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/http/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. + */ + +/** + * HTTP based security APIs. + */ +@NullMarked +package org.springframework.security.web.http; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/jaasapi/JaasApiIntegrationFilter.java b/web/src/main/java/org/springframework/security/web/jaasapi/JaasApiIntegrationFilter.java index c2111d02f5..c0a45ed9db 100644 --- a/web/src/main/java/org/springframework/security/web/jaasapi/JaasApiIntegrationFilter.java +++ b/web/src/main/java/org/springframework/security/web/jaasapi/JaasApiIntegrationFilter.java @@ -27,6 +27,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.jaas.JaasAuthenticationToken; @@ -118,7 +119,7 @@ public class JaasApiIntegrationFilter extends GenericFilterBean { * @return the Subject to run as or null if no Subject is * available. */ - protected Subject obtainSubject(ServletRequest request) { + protected @Nullable Subject obtainSubject(ServletRequest request) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); this.logger.debug(LogMessage.format("Attempting to obtainSubject using authentication : %s", authentication)); if (authentication == null) { diff --git a/web/src/main/java/org/springframework/security/web/jaasapi/package-info.java b/web/src/main/java/org/springframework/security/web/jaasapi/package-info.java index 7b45379652..d2fd39e763 100644 --- a/web/src/main/java/org/springframework/security/web/jaasapi/package-info.java +++ b/web/src/main/java/org/springframework/security/web/jaasapi/package-info.java @@ -20,4 +20,7 @@ * To use, simply add the {@code JaasApiIntegrationFilter} to the Spring Security filter * chain. */ +@NullMarked package org.springframework.security.web.jaasapi; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java index fed3f41463..b69c130d7c 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.savedrequest.DefaultSavedRequest; @@ -45,6 +46,6 @@ import org.springframework.security.web.savedrequest.DefaultSavedRequest; abstract class DefaultSavedRequestMixin { @JsonInclude(JsonInclude.Include.NON_NULL) - String matchingRequestParameterName; + @Nullable String matchingRequestParameterName; } diff --git a/web/src/main/java/org/springframework/security/web/jackson2/package-info.java b/web/src/main/java/org/springframework/security/web/jackson2/package-info.java index 9eedec1254..92dbfadf4f 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/package-info.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/package-info.java @@ -20,4 +20,7 @@ * @author Jitendra Singh * @since 4.2 */ +@NullMarked package org.springframework.security.web.jackson2; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java index afdddadc2e..bdc8b8859e 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java @@ -18,6 +18,8 @@ package org.springframework.security.web.method.annotation; 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; @@ -107,28 +109,27 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet private boolean useAnnotationTemplate = false; - private BeanResolver beanResolver; + private @Nullable BeanResolver beanResolver; @Override - public boolean supportsParameter(MethodParameter parameter) { - return findMethodAnnotation(parameter) != null; - } - - @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { return null; } Object principal = authentication.getPrincipal(); AuthenticationPrincipal annotation = findMethodAnnotation(parameter); + Assert.notNull(annotation, "@AuthenticationPrincipal is required. Call supportsParameter first."); String expressionToParse = annotation.expression(); if (StringUtils.hasLength(expressionToParse)) { StandardEvaluationContext context = new StandardEvaluationContext(); context.setRootObject(principal); context.setVariable("this", principal); - context.setBeanResolver(this.beanResolver); + // https://github.com/spring-projects/spring-framework/issues/35371 + if (this.beanResolver != null) { + context.setBeanResolver(this.beanResolver); + } Expression expression = this.parser.parseExpression(expressionToParse); principal = expression.getValue(context); } @@ -141,6 +142,11 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet return principal; } + @Override + public boolean supportsParameter(MethodParameter parameter) { + return findMethodAnnotation(parameter) != null; + } + /** * Sets the {@link BeanResolver} to be used on the expressions * @param beanResolver the {@link BeanResolver} to use @@ -181,7 +187,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet * @return the {@link Annotation} that was found or null. */ @SuppressWarnings("unchecked") - private AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) { + private @Nullable AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) { if (this.useAnnotationTemplate) { return this.scanner.scan(parameter.getParameter()); } diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/CsrfTokenArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/CsrfTokenArgumentResolver.java index 8a39385062..e954046de3 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/CsrfTokenArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/method/annotation/CsrfTokenArgumentResolver.java @@ -16,6 +16,8 @@ package org.springframework.security.web.method.annotation; +import org.jspecify.annotations.Nullable; + import org.springframework.core.MethodParameter; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.bind.annotation.RestController; @@ -51,8 +53,8 @@ public final class CsrfTokenArgumentResolver implements HandlerMethodArgumentRes } @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { CsrfToken token = (CsrfToken) webRequest.getAttribute(CsrfToken.class.getName(), RequestAttributes.SCOPE_REQUEST); return token; diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java index 6d20893f55..23b89bd09b 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java @@ -18,6 +18,8 @@ package org.springframework.security.web.method.annotation; 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; @@ -93,7 +95,7 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth private boolean useAnnotationTemplate = false; - private BeanResolver beanResolver; + private @Nullable BeanResolver beanResolver; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -102,8 +104,8 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth } @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + public @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { SecurityContext securityContext = this.securityContextHolderStrategy.getContext(); if (securityContext == null) { return null; @@ -150,15 +152,18 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth this.scanner = SecurityAnnotationScanners.requireUnique(CurrentSecurityContext.class, templateDefaults); } - private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, CurrentSecurityContext annotation, - SecurityContext securityContext) { + private @Nullable Object resolveSecurityContextFromAnnotation(MethodParameter parameter, + CurrentSecurityContext annotation, SecurityContext securityContext) { Object securityContextResult = securityContext; String expressionToParse = annotation.expression(); if (StringUtils.hasLength(expressionToParse)) { StandardEvaluationContext context = new StandardEvaluationContext(); context.setRootObject(securityContext); context.setVariable("this", securityContext); - context.setBeanResolver(this.beanResolver); + // https://github.com/spring-projects/spring-framework/issues/35371 + if (this.beanResolver != null) { + context.setBeanResolver(this.beanResolver); + } Expression expression = this.parser.parseExpression(expressionToParse); securityContextResult = expression.getValue(context); } @@ -178,7 +183,7 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth * @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/web/src/main/java/org/springframework/security/web/method/annotation/package-info.java b/web/src/main/java/org/springframework/security/web/method/annotation/package-info.java new file mode 100644 index 0000000000..5cfb1e402f --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/method/annotation/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 Spring Framework's handler method processing. + */ +@NullMarked +package org.springframework.security.web.method.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/package-info.java b/web/src/main/java/org/springframework/security/web/package-info.java index 02f7dc9703..57367b06d1 100644 --- a/web/src/main/java/org/springframework/security/web/package-info.java +++ b/web/src/main/java/org/springframework/security/web/package-info.java @@ -18,4 +18,7 @@ * Spring Security's web security module. Classes which have a dependency on the Servlet * API can be found here. */ +@NullMarked package org.springframework.security.web; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java index 3607f47028..799aa8ece0 100644 --- a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java @@ -18,6 +18,7 @@ package org.springframework.security.web.reactive.result.method.annotation; import java.lang.annotation.Annotation; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -62,7 +63,7 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume private boolean useAnnotationTemplate = false; - private BeanResolver beanResolver; + private @Nullable BeanResolver beanResolver; public AuthenticationPrincipalArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -93,8 +94,13 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume }); } - private Object resolvePrincipal(MethodParameter parameter, Object principal) { + @SuppressWarnings("NullAway") // https://github.com/spring-projects/spring-framework/issues/35371 + private @Nullable Object resolvePrincipal(MethodParameter parameter, @Nullable Object principal) { AuthenticationPrincipal annotation = findMethodAnnotation(parameter); + if (annotation == null) { + // FIXME: Add test + return null; + } String expressionToParse = annotation.expression(); if (StringUtils.hasLength(expressionToParse)) { StandardEvaluationContext context = new StandardEvaluationContext(); @@ -113,7 +119,7 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume return principal; } - private boolean isInvalidType(MethodParameter parameter, Object principal) { + private boolean isInvalidType(MethodParameter parameter, @Nullable Object principal) { if (principal == null) { return false; } @@ -150,7 +156,7 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume * @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/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java index b988fe97f5..c085b57932 100644 --- a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java @@ -18,6 +18,7 @@ package org.springframework.security.web.reactive.result.method.annotation; import java.lang.annotation.Annotation; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -62,7 +63,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen private boolean useAnnotationTemplate = false; - private BeanResolver beanResolver; + private @Nullable BeanResolver beanResolver; public CurrentSecurityContextArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -115,7 +116,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen ReactiveAdapter adapter = getAdapterRegistry().getAdapter(parameter.getParameterType()); Mono reactiveSecurityContext = ReactiveSecurityContextHolder.getContext(); if (reactiveSecurityContext == null) { - return null; + return Mono.empty(); } return reactiveSecurityContext.flatMap((securityContext) -> { Mono resolvedSecurityContext = Mono.justOrEmpty(resolveSecurityContext(parameter, securityContext)); @@ -132,7 +133,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen * @param securityContext the security context. * @return the resolved object from expression. */ - private Object resolveSecurityContext(MethodParameter parameter, SecurityContext securityContext) { + private @Nullable Object resolveSecurityContext(MethodParameter parameter, SecurityContext securityContext) { CurrentSecurityContext annotation = findMethodAnnotation(parameter); if (annotation != null) { return resolveSecurityContextFromAnnotation(annotation, parameter, securityContext); @@ -140,8 +141,9 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen return securityContext; } - private Object resolveSecurityContextFromAnnotation(CurrentSecurityContext annotation, MethodParameter parameter, - Object securityContext) { + @SuppressWarnings("NullAway") // https://github.com/spring-projects/spring-framework/issues/35371 + private @Nullable Object resolveSecurityContextFromAnnotation(CurrentSecurityContext annotation, + MethodParameter parameter, Object securityContext) { Object securityContextResult = securityContext; String expressionToParse = annotation.expression(); if (StringUtils.hasLength(expressionToParse)) { @@ -168,7 +170,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen * @param reactiveSecurityContext the security context. * @return true = is not invalid type. */ - private boolean isInvalidType(MethodParameter parameter, Object reactiveSecurityContext) { + private boolean isInvalidType(MethodParameter parameter, @Nullable Object reactiveSecurityContext) { if (reactiveSecurityContext == null) { return false; } @@ -190,7 +192,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen * @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/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/package-info.java b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/package-info.java new file mode 100644 index 0000000000..54274935f0 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/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 Spring Framework's reactive handler method processing. + */ +@NullMarked +package org.springframework.security.web.reactive.result.method.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/reactive/result/view/package-info.java b/web/src/main/java/org/springframework/security/web/reactive/result/view/package-info.java new file mode 100644 index 0000000000..81cd1cb221 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/reactive/result/view/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 Spring Framework's reactive view processing. + */ +@NullMarked +package org.springframework.security.web.reactive.result.view; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/CookieRequestCache.java b/web/src/main/java/org/springframework/security/web/savedrequest/CookieRequestCache.java index 297dea8bb7..7ea903cf20 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/CookieRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/CookieRequestCache.java @@ -18,6 +18,7 @@ package org.springframework.security.web.savedrequest; import java.util.Base64; import java.util.Collections; +import java.util.Objects; import java.util.function.Consumer; import jakarta.servlet.http.Cookie; @@ -25,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AnyRequestMatcher; @@ -72,7 +74,7 @@ public class CookieRequestCache implements RequestCache { } @Override - public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) { + public @Nullable SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) { Cookie savedRequestCookie = WebUtils.getCookie(request, COOKIE_NAME); if (savedRequestCookie == null) { return null; @@ -106,14 +108,15 @@ public class CookieRequestCache implements RequestCache { } @Override - public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { - SavedRequest saved = this.getRequest(request, response); + public @Nullable HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { + @Nullable SavedRequest saved = this.getRequest(request, response); if (!this.matchesSavedRequest(request, saved)) { this.logger.debug("saved request doesn't match"); return null; } this.removeRequest(request, response); - return new SavedRequestAwareWrapper(saved, request); + // we know that saved is non-null because matchesSavedRequest = true + return new SavedRequestAwareWrapper(Objects.requireNonNull(saved), request); } @Override @@ -130,7 +133,7 @@ public class CookieRequestCache implements RequestCache { return Base64.getEncoder().encodeToString(cookieValue.getBytes()); } - private String decodeCookie(String encodedCookieValue) { + private @Nullable String decodeCookie(String encodedCookieValue) { try { return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes())); } @@ -145,7 +148,7 @@ public class CookieRequestCache implements RequestCache { return (StringUtils.hasLength(contextPath)) ? contextPath : "/"; } - private boolean matchesSavedRequest(HttpServletRequest request, SavedRequest savedRequest) { + private boolean matchesSavedRequest(HttpServletRequest request, @Nullable SavedRequest savedRequest) { if (savedRequest == null) { return false; } diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java b/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java index 2a9c5e52d5..f522e1e847 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java @@ -23,6 +23,7 @@ import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -31,6 +32,7 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.PortResolver; import org.springframework.security.web.util.UrlUtils; @@ -77,33 +79,33 @@ public class DefaultSavedRequest implements SavedRequest { private final Map parameters = new TreeMap<>(); - private final String contextPath; + private final @Nullable String contextPath; private final String method; - private final String pathInfo; + private final @Nullable String pathInfo; - private final String queryString; + private final @Nullable String queryString; private final String requestURI; - private final String requestURL; + private final @Nullable String requestURL; private final String scheme; private final String serverName; - private final String servletPath; + private final @Nullable String servletPath; private final int serverPort; - private final String matchingRequestParameterName; + private final @Nullable String matchingRequestParameterName; public DefaultSavedRequest(HttpServletRequest request) { this(request, (String) null); } - public DefaultSavedRequest(HttpServletRequest request, String matchingRequestParameterName) { + public DefaultSavedRequest(HttpServletRequest request, @Nullable String matchingRequestParameterName) { this(request, PortResolver.NO_OP, matchingRequestParameterName); } @@ -115,7 +117,7 @@ public class DefaultSavedRequest implements SavedRequest { @SuppressWarnings("unchecked") @Deprecated(forRemoval = true) public DefaultSavedRequest(HttpServletRequest request, PortResolver portResolver, - String matchingRequestParameterName) { + @Nullable String matchingRequestParameterName) { Assert.notNull(request, "Request required"); Assert.notNull(portResolver, "PortResolver required"); // Cookies @@ -156,13 +158,13 @@ public class DefaultSavedRequest implements SavedRequest { */ private DefaultSavedRequest(Builder builder) { this.contextPath = builder.contextPath; - this.method = builder.method; + this.method = (builder.method != null) ? builder.method : "GET"; this.pathInfo = builder.pathInfo; this.queryString = builder.queryString; - this.requestURI = builder.requestURI; + this.requestURI = Objects.requireNonNull(builder.requestURI); this.requestURL = builder.requestURL; - this.scheme = builder.scheme; - this.serverName = builder.serverName; + this.scheme = Objects.requireNonNull(builder.scheme); + this.serverName = Objects.requireNonNull(builder.serverName); this.servletPath = builder.servletPath; this.serverPort = builder.serverPort; this.matchingRequestParameterName = builder.matchingRequestParameterName; @@ -268,7 +270,7 @@ public class DefaultSavedRequest implements SavedRequest { } - public String getContextPath() { + public @Nullable String getContextPath() { return this.contextPath; } @@ -323,31 +325,31 @@ public class DefaultSavedRequest implements SavedRequest { } @Override - public String[] getParameterValues(String name) { + public String @Nullable [] getParameterValues(String name) { return this.parameters.get(name); } - public String getPathInfo() { + public @Nullable String getPathInfo() { return this.pathInfo; } - public String getQueryString() { + public @Nullable String getQueryString() { return (this.queryString); } - public String getRequestURI() { + public @Nullable String getRequestURI() { return (this.requestURI); } - public String getRequestURL() { + public @Nullable String getRequestURL() { return this.requestURL; } - public String getScheme() { + public @Nullable String getScheme() { return this.scheme; } - public String getServerName() { + public @Nullable String getServerName() { return this.serverName; } @@ -355,11 +357,11 @@ public class DefaultSavedRequest implements SavedRequest { return this.serverPort; } - public String getServletPath() { + public @Nullable String getServletPath() { return this.servletPath; } - private boolean propertyEquals(Object arg1, Object arg2) { + private boolean propertyEquals(@Nullable Object arg1, Object arg2) { if ((arg1 == null) && (arg2 == null)) { return true; } @@ -374,7 +376,8 @@ public class DefaultSavedRequest implements SavedRequest { return "DefaultSavedRequest [" + getRedirectUrl() + "]"; } - private static String createQueryString(String queryString, String matchingRequestParameterName) { + private static @Nullable String createQueryString(@Nullable String queryString, + @Nullable String matchingRequestParameterName) { if (matchingRequestParameterName == null) { return queryString; } @@ -396,35 +399,35 @@ public class DefaultSavedRequest implements SavedRequest { @JsonPOJOBuilder(withPrefix = "set") public static class Builder { - private List cookies = null; + private @Nullable List cookies = null; - private List locales = null; + private @Nullable List locales = null; private Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private Map parameters = new TreeMap<>(); - private String contextPath; + private @Nullable String contextPath; - private String method; + private @Nullable String method; - private String pathInfo; + private @Nullable String pathInfo; - private String queryString; + private @Nullable String queryString; - private String requestURI; + private @Nullable String requestURI; - private String requestURL; + private @Nullable String requestURL; - private String scheme; + private @Nullable String scheme; - private String serverName; + private @Nullable String serverName; - private String servletPath; + private @Nullable String servletPath; private int serverPort = 80; - private String matchingRequestParameterName; + private @Nullable String matchingRequestParameterName; public Builder setCookies(List cookies) { this.cookies = cookies; @@ -461,12 +464,12 @@ public class DefaultSavedRequest implements SavedRequest { return this; } - public Builder setQueryString(String queryString) { + public Builder setQueryString(@Nullable String queryString) { this.queryString = queryString; return this; } - public Builder setRequestURI(String requestURI) { + public Builder setRequestURI(@Nullable String requestURI) { this.requestURI = requestURI; return this; } @@ -476,12 +479,12 @@ public class DefaultSavedRequest implements SavedRequest { return this; } - public Builder setScheme(String scheme) { + public Builder setScheme(@Nullable String scheme) { this.scheme = scheme; return this; } - public Builder setServerName(String serverName) { + public Builder setServerName(@Nullable String serverName) { this.serverName = serverName; return this; } diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/Enumerator.java b/web/src/main/java/org/springframework/security/web/savedrequest/Enumerator.java index fdcb553b55..408ef2b3fe 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/Enumerator.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/Enumerator.java @@ -45,6 +45,7 @@ public class Enumerator implements Enumeration { * The Iterator over which the Enumeration represented by * this class actually operates. */ + @SuppressWarnings("NullAway") private Iterator iterator = null; /** diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/FastHttpDateFormat.java b/web/src/main/java/org/springframework/security/web/savedrequest/FastHttpDateFormat.java index dd11a3c5b0..9c0d0dbf23 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/FastHttpDateFormat.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/FastHttpDateFormat.java @@ -24,6 +24,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.TimeZone; +import org.jspecify.annotations.Nullable; + /** * Utility class to generate HTTP dates. *

    @@ -67,7 +69,7 @@ public final class FastHttpDateFormat { /** * Current formatted date. */ - protected static String currentDate = null; + protected static @Nullable String currentDate = null; /** * Formatter cache. @@ -122,7 +124,7 @@ public final class FastHttpDateFormat { * Gets the current date in HTTP format. * @return Current date in HTTP format */ - public static String getCurrentDate() { + public static @Nullable String getCurrentDate() { long now = System.currentTimeMillis(); if ((now - currentDateGenerated) > 1000) { synchronized (format) { @@ -141,7 +143,7 @@ public final class FastHttpDateFormat { * @param formats Array of formats to use * @return Parsed date (or null if no formatter mached) */ - private static Long internalParseDate(String value, DateFormat[] formats) { + private static @Nullable Long internalParseDate(String value, DateFormat[] formats) { Date date = null; for (int i = 0; (date == null) && (i < formats.length); i++) { try { @@ -197,7 +199,7 @@ public final class FastHttpDateFormat { * @param value New value */ @SuppressWarnings("unchecked") - private static void updateCache(HashMap cache, Object key, Object value) { + private static void updateCache(HashMap cache, Object key, @Nullable Object value) { if (value == null) { return; } diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java b/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java index 9e51943f72..af7787bd39 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; 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.security.web.PortResolver; @@ -86,7 +87,7 @@ public class HttpSessionRequestCache implements RequestCache { } @Override - public SavedRequest getRequest(HttpServletRequest currentRequest, HttpServletResponse response) { + public @Nullable SavedRequest getRequest(HttpServletRequest currentRequest, HttpServletResponse response) { HttpSession session = currentRequest.getSession(false); return (session != null) ? (SavedRequest) session.getAttribute(this.sessionAttrName) : null; } @@ -101,7 +102,7 @@ public class HttpSessionRequestCache implements RequestCache { } @Override - public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { + public @Nullable HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { if (this.matchingRequestParameterName != null) { if (!StringUtils.hasText(request.getQueryString()) || !UriComponentsBuilder.fromUriString(UrlUtils.buildRequestUrl(request)) diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/NullRequestCache.java b/web/src/main/java/org/springframework/security/web/savedrequest/NullRequestCache.java index 2c83675f95..c30dffdb67 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/NullRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/NullRequestCache.java @@ -18,6 +18,7 @@ package org.springframework.security.web.savedrequest; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; /** * Null implementation of RequestCache. Typically used when creation of a session @@ -29,7 +30,7 @@ import jakarta.servlet.http.HttpServletResponse; public class NullRequestCache implements RequestCache { @Override - public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) { + public @Nullable SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) { return null; } @@ -43,7 +44,7 @@ public class NullRequestCache implements RequestCache { } @Override - public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { + public @Nullable HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { return null; } diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/RequestCache.java b/web/src/main/java/org/springframework/security/web/savedrequest/RequestCache.java index 06e3b74df1..d957822f47 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/RequestCache.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/RequestCache.java @@ -18,6 +18,7 @@ package org.springframework.security.web.savedrequest; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; /** * Implements "saved request" logic, allowing a single request to be retrieved and @@ -40,7 +41,7 @@ public interface RequestCache { * @param request the current request * @return the saved request which was previously cached, or null if there is none. */ - SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response); + @Nullable SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response); /** * Returns a wrapper around the saved request, if it matches the current request. The @@ -50,7 +51,7 @@ public interface RequestCache { * @return the wrapped save request, if it matches the original, or null if there is * no cached request or it doesn't match. */ - HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response); + @Nullable HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response); /** * Removes the cached request. diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequest.java b/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequest.java index 4618395728..4a59e171d8 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequest.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequest.java @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.Map; import jakarta.servlet.http.Cookie; +import org.jspecify.annotations.Nullable; /** * Encapsulates the functionality required of a cached request for both an authentication @@ -49,7 +50,7 @@ public interface SavedRequest extends java.io.Serializable { List getLocales(); - String[] getParameterValues(String name); + String @Nullable [] getParameterValues(String name); Map getParameterMap(); diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequestAwareWrapper.java b/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequestAwareWrapper.java index 77c5207cd8..10d21f19d8 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequestAwareWrapper.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/SavedRequestAwareWrapper.java @@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpHeaders; @@ -94,7 +95,7 @@ class SavedRequestAwareWrapper extends HttpServletRequestWrapper { } @Override - public String getHeader(String name) { + public @Nullable String getHeader(String name) { List values = this.savedRequest.getHeaderValues(name); return values.isEmpty() ? null : values.get(0); } @@ -133,12 +134,12 @@ class SavedRequestAwareWrapper extends HttpServletRequestWrapper { } @Override - public String getMethod() { + public @Nullable String getMethod() { return this.savedRequest.getMethod(); } @Override - public String getContentType() { + public @Nullable String getContentType() { return getHeader(HttpHeaders.CONTENT_TYPE); } @@ -153,7 +154,7 @@ class SavedRequestAwareWrapper extends HttpServletRequestWrapper { * the parameter from the saved request. */ @Override - public String getParameter(String name) { + public @Nullable String getParameter(String name) { String value = super.getParameter(name); if (value != null) { return value; diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/SimpleSavedRequest.java b/web/src/main/java/org/springframework/security/web/savedrequest/SimpleSavedRequest.java index e74e7fcb11..fc2169f124 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/SimpleSavedRequest.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/SimpleSavedRequest.java @@ -52,6 +52,8 @@ public class SimpleSavedRequest implements SavedRequest { private Map parameters = new HashMap<>(); public SimpleSavedRequest() { + this.redirectUrl = "/"; + this.method = "GET"; } public SimpleSavedRequest(String redirectUrl) { diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/package-info.java b/web/src/main/java/org/springframework/security/web/savedrequest/package-info.java index 0dd034e90e..13070e6b25 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/package-info.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/package-info.java @@ -21,4 +21,7 @@ * authenticated, the original request is restored following a redirect to a matching URL, * and the {@code RequestCache} is queried to obtain the original (matching) request. */ +@NullMarked package org.springframework.security.web.savedrequest; + +import org.jspecify.annotations.NullMarked; 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 e4de5579d8..8c473a07d4 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,9 +26,10 @@ 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.NullUnmarked; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; @@ -44,6 +45,7 @@ import org.springframework.web.server.WebHandler; * @author Josh Cummings * @since 6.0 */ +@NullUnmarked // https://github.com/spring-projects/spring-security/issues/17815 public final class ObservationWebFilterChainDecorator implements WebFilterChainProxy.WebFilterChainDecorator { private static final String ATTRIBUTE = ObservationWebFilterChainDecorator.class + ".observation"; @@ -68,7 +70,7 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP return new ObservationWebFilterChain(wrapSecured(original)::filter, wrap(filters)); } - private static AroundWebFilterObservation observation(ServerWebExchange exchange) { + private static @Nullable AroundWebFilterObservation observation(ServerWebExchange exchange) { return exchange.getAttribute(ATTRIBUTE); } @@ -108,11 +110,9 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP private final WebHandler handler; - @Nullable - private final ObservationWebFilter currentFilter; + @Nullable private final ObservationWebFilter currentFilter; - @Nullable - private final ObservationWebFilterChain chain; + @Nullable private final ObservationWebFilterChain chain; /** * Public constructor with the list of filters and the target handler to use. @@ -303,12 +303,12 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP } @Override - public Observation contextualName(String contextualName) { + public Observation contextualName(@Nullable String contextualName) { return this.currentObservation.observation.contextualName(contextualName); } @Override - public Observation parentObservation(Observation parentObservation) { + public Observation parentObservation(@Nullable Observation parentObservation) { return this.currentObservation.observation.parentObservation(parentObservation); } @@ -407,12 +407,12 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP } @Override - default Observation contextualName(String contextualName) { + default Observation contextualName(@Nullable String contextualName) { return Observation.NOOP; } @Override - default Observation parentObservation(Observation parentObservation) { + default Observation parentObservation(@Nullable Observation parentObservation) { return Observation.NOOP; } @@ -493,12 +493,12 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP } @Override - public Observation contextualName(String contextualName) { + public Observation contextualName(@Nullable String contextualName) { return this.observation.contextualName(contextualName); } @Override - public Observation parentObservation(Observation parentObservation) { + public Observation parentObservation(@Nullable Observation parentObservation) { return this.observation.parentObservation(parentObservation); } @@ -575,7 +575,7 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP private final String filterSection; - private String filterName; + private @Nullable String filterName; private int chainPosition; @@ -597,7 +597,7 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP return this.filterSection; } - String getFilterName() { + @Nullable String getFilterName() { return this.filterName; } @@ -677,12 +677,12 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP } @Override - public Observation contextualName(String contextualName) { + public Observation contextualName(@Nullable String contextualName) { return this.observation.contextualName(contextualName); } @Override - public Observation parentObservation(Observation parentObservation) { + public Observation parentObservation(@Nullable Observation parentObservation) { return this.observation.parentObservation(parentObservation); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java index 0bfde54db2..db3a4e2bfd 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java @@ -17,6 +17,7 @@ package org.springframework.security.web.server.authentication; import java.util.List; +import java.util.Objects; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; @@ -65,7 +66,7 @@ public final class ConcurrentSessionControlServerAuthenticationSuccessHandler private Mono handleConcurrency(WebFilterExchange exchange, Authentication authentication, Integer maximumSessions) { - return this.sessionRegistry.getAllSessions(authentication.getPrincipal()) + return this.sessionRegistry.getAllSessions(Objects.requireNonNull(authentication.getPrincipal())) .collectList() .flatMap((registeredSessions) -> exchange.getExchange() .getSession() diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java index c322e44dd8..32515b9d5e 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.authentication; +import java.util.Objects; + import reactor.core.publisher.Mono; import org.springframework.security.core.Authentication; @@ -44,8 +46,8 @@ public final class RegisterSessionServerAuthenticationSuccessHandler implements public Mono onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) { return exchange.getExchange() .getSession() - .map((session) -> new ReactiveSessionInformation(authentication.getPrincipal(), session.getId(), - session.getLastAccessTime())) + .map((session) -> new ReactiveSessionInformation(Objects.requireNonNull(authentication.getPrincipal()), + session.getId(), session.getLastAccessTime())) .flatMap(this.sessionRegistry::saveSessionInformation); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java index d6d1f6bc8c..c80ea7f83d 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/SwitchUserWebFilter.java @@ -101,7 +101,7 @@ public class SwitchUserWebFilter implements WebFilter { private final ServerAuthenticationSuccessHandler successHandler; - private final ServerAuthenticationFailureHandler failureHandler; + private final @Nullable ServerAuthenticationFailureHandler failureHandler; private final ReactiveUserDetailsService userDetailsService; @@ -211,12 +211,12 @@ public class SwitchUserWebFilter implements WebFilter { * @param exchange The server web exchange * @return the name of the target user. */ - protected String getUsername(ServerWebExchange exchange) { + protected @Nullable String getUsername(ServerWebExchange exchange) { return exchange.getRequest().getQueryParams().getFirst(SPRING_SECURITY_SWITCH_USERNAME_KEY); } @NonNull - private Mono attemptSwitchUser(Authentication currentAuthentication, String userName) { + private Mono attemptSwitchUser(Authentication currentAuthentication, @Nullable String userName) { Assert.notNull(userName, "The userName can not be null."); this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", userName)); return this.userDetailsService.findByUsername(userName) diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/logout/package-info.java b/web/src/main/java/org/springframework/security/web/server/authentication/logout/package-info.java new file mode 100644 index 0000000000..4929a64eb4 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/authentication/logout/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 logout APIs. + */ +@NullMarked +package org.springframework.security.web.server.authentication.logout; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ott/ServerOneTimeTokenAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/server/authentication/ott/ServerOneTimeTokenAuthenticationConverter.java index d5e2da6afc..a0a539645d 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/ott/ServerOneTimeTokenAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ott/ServerOneTimeTokenAuthenticationConverter.java @@ -18,6 +18,7 @@ package org.springframework.security.web.server.authentication.ott; import java.util.List; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; @@ -58,7 +59,7 @@ public final class ServerOneTimeTokenAuthenticationConverter implements ServerAu return Mono.just(OneTimeTokenAuthenticationToken.unauthenticated(token)); } - private String resolveTokenFromRequest(ServerHttpRequest request) { + private @Nullable String resolveTokenFromRequest(ServerHttpRequest request) { List parameterTokens = request.getQueryParams().get(TOKEN); if (CollectionUtils.isEmpty(parameterTokens)) { return null; diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ott/package-info.java b/web/src/main/java/org/springframework/security/web/server/authentication/ott/package-info.java new file mode 100644 index 0000000000..e1a9b9bc81 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ott/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 OTT APIs. + */ +@NullMarked +package org.springframework.security.web.server.authentication.ott; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/package-info.java b/web/src/main/java/org/springframework/security/web/server/authentication/package-info.java new file mode 100644 index 0000000000..ea5cfb5ca5 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/authentication/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 web Authorization APIs. + */ +@NullMarked +package org.springframework.security.web.server.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java index d0ad0d1047..3286a5ae45 100644 --- a/web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.authorization; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.http.HttpStatus; @@ -97,7 +98,7 @@ public class ExceptionTranslationWebFilter implements WebFilter { this.authenticationTrustResolver = authenticationTrustResolver; } - private Mono commenceAuthentication(ServerWebExchange exchange, Authentication authentication) { + private Mono commenceAuthentication(ServerWebExchange exchange, @Nullable Authentication authentication) { AuthenticationException cause = new InsufficientAuthenticationException( "Full authentication is required to access this resource"); AuthenticationException ex = new AuthenticationCredentialsNotFoundException("Not Authenticated", cause); diff --git a/web/src/main/java/org/springframework/security/web/server/authorization/package-info.java b/web/src/main/java/org/springframework/security/web/server/authorization/package-info.java new file mode 100644 index 0000000000..aea5499ef0 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/authorization/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 web OTT APIs. + */ +@NullMarked +package org.springframework.security.web.server.authorization; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/context/NoOpServerSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/server/context/NoOpServerSecurityContextRepository.java index 37a0e86810..e7b66bc218 100644 --- a/web/src/main/java/org/springframework/security/web/server/context/NoOpServerSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/context/NoOpServerSecurityContextRepository.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.context; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.core.context.SecurityContext; @@ -36,7 +37,7 @@ public final class NoOpServerSecurityContextRepository implements ServerSecurity } @Override - public Mono save(ServerWebExchange exchange, SecurityContext context) { + public Mono save(ServerWebExchange exchange, @Nullable SecurityContext context) { return Mono.empty(); } diff --git a/web/src/main/java/org/springframework/security/web/server/context/ServerSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/server/context/ServerSecurityContextRepository.java index efa1724dfd..970f56e3ef 100644 --- a/web/src/main/java/org/springframework/security/web/server/context/ServerSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/context/ServerSecurityContextRepository.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.context; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.core.context.SecurityContext; @@ -36,7 +37,7 @@ public interface ServerSecurityContextRepository { * @param context the SecurityContext to save * @return a completion notification (success or error) */ - Mono save(ServerWebExchange exchange, SecurityContext context); + Mono save(ServerWebExchange exchange, @Nullable SecurityContext context); /** * Loads the SecurityContext associated with the {@link ServerWebExchange} diff --git a/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java index cdaa67bf6f..2f447dfec8 100644 --- a/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java @@ -18,6 +18,7 @@ package org.springframework.security.web.server.context; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.log.LogMessage; @@ -69,7 +70,7 @@ public class WebSessionServerSecurityContextRepository implements ServerSecurity } @Override - public Mono save(ServerWebExchange exchange, SecurityContext context) { + public Mono save(ServerWebExchange exchange, @Nullable SecurityContext context) { return exchange.getSession().doOnNext((session) -> { if (context == null) { session.getAttributes().remove(this.springSecurityContextAttrName); diff --git a/web/src/main/java/org/springframework/security/web/server/context/package-info.java b/web/src/main/java/org/springframework/security/web/server/context/package-info.java new file mode 100644 index 0000000000..a45c371d2c --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/context/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 web context APIs. + */ +@NullMarked +package org.springframework.security.web.server.context; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java index fa45ba6b82..fbd597d6c9 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java @@ -19,6 +19,7 @@ package org.springframework.security.web.server.csrf; import java.util.UUID; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -50,15 +51,15 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep private String headerName = DEFAULT_CSRF_HEADER_NAME; - private String cookiePath; + private @Nullable String cookiePath; - private String cookieDomain; + private @Nullable String cookieDomain; private String cookieName = DEFAULT_CSRF_COOKIE_NAME; private boolean cookieHttpOnly = true; - private Boolean secure; + private @Nullable Boolean secure; private int cookieMaxAge = -1; @@ -94,7 +95,7 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep } @Override - public Mono saveToken(ServerWebExchange exchange, CsrfToken token) { + public Mono saveToken(ServerWebExchange exchange, @Nullable CsrfToken token) { return Mono.fromRunnable(() -> { String tokenValue = (token != null) ? token.getToken() : ""; // @formatter:off diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRepository.java index e90573911d..a1a3800794 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/ServerCsrfTokenRepository.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.csrf; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.web.server.ServerWebExchange; @@ -45,7 +46,7 @@ public interface ServerCsrfTokenRepository { * @param exchange the {@link ServerWebExchange} to use * @param token the {@link CsrfToken} to save or null to delete */ - Mono saveToken(ServerWebExchange exchange, CsrfToken token); + Mono saveToken(ServerWebExchange exchange, @Nullable CsrfToken token); /** * Loads the expected {@link CsrfToken} from the {@link ServerWebExchange} diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java index 162fce51b7..5466267035 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java @@ -19,6 +19,7 @@ package org.springframework.security.web.server.csrf; import java.util.Map; import java.util.UUID; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -54,13 +55,13 @@ public class WebSessionServerCsrfTokenRepository implements ServerCsrfTokenRepos } @Override - public Mono saveToken(ServerWebExchange exchange, CsrfToken token) { + public Mono saveToken(ServerWebExchange exchange, @Nullable CsrfToken token) { return exchange.getSession() .doOnNext((session) -> putToken(session.getAttributes(), token)) .flatMap((session) -> session.changeSessionId()); } - private void putToken(Map attributes, CsrfToken token) { + private void putToken(Map attributes, @Nullable CsrfToken token) { if (token == null) { attributes.remove(this.sessionAttributeName); } diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java b/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java index 563ab41b87..012f5ea6f8 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java @@ -21,6 +21,7 @@ import java.util.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.log.LogMessage; @@ -72,7 +73,7 @@ public final class XorServerCsrfTokenRequestAttributeHandler extends ServerCsrfT .flatMap((actualToken) -> Mono.justOrEmpty(getTokenValue(actualToken, csrfToken.getToken()))); } - private static String getTokenValue(String actualToken, String token) { + private static @Nullable String getTokenValue(String actualToken, String token) { byte[] actualBytes; try { actualBytes = Base64.getUrlDecoder().decode(actualToken); diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/package-info.java b/web/src/main/java/org/springframework/security/web/server/csrf/package-info.java new file mode 100644 index 0000000000..36554692c8 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/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. + */ + +/** + * Reactive APIs for protecting against CSRF attacks. + */ +@NullMarked +package org.springframework.security.web.server.csrf; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java b/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java index 1945d47e97..ec1f12ad26 100644 --- a/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java +++ b/web/src/main/java/org/springframework/security/web/server/firewall/StrictServerWebExchangeFirewall.java @@ -29,6 +29,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; @@ -636,7 +637,7 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal } } - private void validateAllowedHeaderValue(Object key, String value) { + private void validateAllowedHeaderValue(Object key, @Nullable String value) { if (!StrictServerWebExchangeFirewall.this.allowedHeaderValues.test(value)) { throw new ServerExchangeRejectedException("The request was rejected because the header: \"" + key + " \" has a value \"" + value + "\" that is not allowed."); @@ -757,7 +758,7 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal } @Override - public String getFirst(String headerName) { + public @Nullable String getFirst(String headerName) { validateAllowedHeaderName(headerName); String headerValue = super.getFirst(headerName); validateAllowedHeaderValue(headerName, headerValue); @@ -765,7 +766,7 @@ public class StrictServerWebExchangeFirewall implements ServerWebExchangeFirewal } @Override - public List get(String headerName) { + public @Nullable List get(String headerName) { validateAllowedHeaderName(headerName); List headerValues = super.get(headerName); if (headerValues == null) { diff --git a/web/src/main/java/org/springframework/security/web/server/firewall/package-info.java b/web/src/main/java/org/springframework/security/web/server/firewall/package-info.java new file mode 100644 index 0000000000..52657ae494 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/firewall/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 HTTP Firewall APIs. + */ +@NullMarked +package org.springframework.security.web.server.firewall; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriter.java index d584e0ed0d..eafd476041 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/ContentSecurityPolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.web.server.header.StaticServerHttpHeadersWriter.Builder; @@ -35,11 +36,11 @@ public final class ContentSecurityPolicyServerHttpHeadersWriter implements Serve public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"; - private String policyDirectives; + private @Nullable String policyDirectives; private boolean reportOnly; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; @Override public Mono writeHttpHeaders(ServerWebExchange exchange) { @@ -67,7 +68,7 @@ public final class ContentSecurityPolicyServerHttpHeadersWriter implements Serve this.delegate = createDelegate(); } - private ServerHttpHeadersWriter createDelegate() { + private @Nullable ServerHttpHeadersWriter createDelegate() { if (this.policyDirectives == null) { return null; } diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java index b170af6a3d..3fc76e7b1c 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.util.Assert; @@ -34,7 +35,7 @@ public final class CrossOriginEmbedderPolicyServerHttpHeadersWriter implements S public static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; /** * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java index 08951565a3..6305e57587 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.util.Assert; @@ -34,7 +35,7 @@ public final class CrossOriginOpenerPolicyServerHttpHeadersWriter implements Ser public static final String OPENER_POLICY = "Cross-Origin-Opener-Policy"; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; /** * Sets the {@link CrossOriginOpenerPolicy} value to be used in the diff --git a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java index 1a8b0b81f0..111bb8a435 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.util.Assert; @@ -34,7 +35,7 @@ public final class CrossOriginResourcePolicyServerHttpHeadersWriter implements S public static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; /** * Sets the {@link CrossOriginResourcePolicy} value to be used in the diff --git a/web/src/main/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriter.java index 9b8d142b7f..7dcebfb962 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/FeaturePolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.web.server.header.StaticServerHttpHeadersWriter.Builder; @@ -32,7 +33,7 @@ public final class FeaturePolicyServerHttpHeadersWriter implements ServerHttpHea public static final String FEATURE_POLICY = "Feature-Policy"; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; @Override public Mono writeHttpHeaders(ServerWebExchange exchange) { diff --git a/web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java index f241ae9aa1..fde4e4ea4f 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.header; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.security.web.server.header.StaticServerHttpHeadersWriter.Builder; @@ -33,7 +34,7 @@ public final class PermissionsPolicyServerHttpHeadersWriter implements ServerHtt public static final String PERMISSIONS_POLICY = "Permissions-Policy"; - private ServerHttpHeadersWriter delegate; + private @Nullable ServerHttpHeadersWriter delegate; @Override public Mono writeHttpHeaders(ServerWebExchange exchange) { diff --git a/web/src/main/java/org/springframework/security/web/server/header/package-info.java b/web/src/main/java/org/springframework/security/web/server/header/package-info.java new file mode 100644 index 0000000000..4cc49788fe --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/header/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 APIs for adding HTTP Header based security. + */ +@NullMarked +package org.springframework.security.web.server.header; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/jackson2/package-info.java b/web/src/main/java/org/springframework/security/web/server/jackson2/package-info.java new file mode 100644 index 0000000000..30c26db3ea --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/jackson2/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 web jackson2 integration. + */ +@NullMarked +package org.springframework.security.web.server.jackson2; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/package-info.java b/web/src/main/java/org/springframework/security/web/server/package-info.java new file mode 100644 index 0000000000..1bc9e5b16b --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/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. + */ + +/** + * WebFlux Spring Security support. + */ +@NullMarked +package org.springframework.security.web.server; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java index 87f0c4a4e4..f3d2ea915d 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java @@ -24,6 +24,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.log.LogMessage; @@ -131,8 +132,9 @@ public class CookieServerRequestCache implements ServerRequestCache { } private static ResponseCookie.ResponseCookieBuilder createResponseCookieBuilder(ServerHttpRequest request, - String cookieValue, Duration age) { - return ResponseCookie.from(REDIRECT_URI_COOKIE_NAME, cookieValue) + @Nullable String cookieValue, Duration age) { + return ResponseCookie.from(REDIRECT_URI_COOKIE_NAME) + .value(cookieValue) .path(request.getPath().contextPath().value() + "/") .maxAge(age) .httpOnly(true) diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java index 83d441a8dd..723e20dcdf 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java @@ -21,6 +21,7 @@ import java.util.Collections; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.log.LogMessage; @@ -59,7 +60,7 @@ public class WebSessionServerRequestCache implements ServerRequestCache { private ServerWebExchangeMatcher saveRequestMatcher = createDefaultRequestMatcher(); - private String matchingRequestParameterName; + private @Nullable String matchingRequestParameterName; /** * Sets the matcher to determine if the request should be saved. The default is to diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/package-info.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/package-info.java new file mode 100644 index 0000000000..7fe13b5d78 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/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. + */ + +/** + * Reactive support for saving requests (to replay them after interrupted by security + * workflows like authentication). + */ +@NullMarked +package org.springframework.security.web.server.savedrequest; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/transport/package-info.java b/web/src/main/java/org/springframework/security/web/server/transport/package-info.java new file mode 100644 index 0000000000..393ef75a99 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/transport/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. + */ + +/** + * WebFlux based transport security. + */ +@NullMarked +package org.springframework.security.web.server.transport; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java index 8b15d0ee54..f9891fe0f8 100644 --- a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; @@ -55,7 +56,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter { private boolean oneTimeTokenEnabled = false; - private String generateOneTimeTokenUrl; + private @Nullable String generateOneTimeTokenUrl; /** * Specifies the URL that a One-Time Token generate request will be processed. diff --git a/web/src/main/java/org/springframework/security/web/server/ui/package-info.java b/web/src/main/java/org/springframework/security/web/server/ui/package-info.java new file mode 100644 index 0000000000..0d886fde89 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/ui/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 rendering UIs (e.g. default log in pages) in WebFlux. + */ +@NullMarked +package org.springframework.security.web.server.ui; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java index 8f5da0e1c3..f3ac8e50c9 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.http.HttpMethod; @@ -44,19 +45,19 @@ public final class PathPatternParserServerWebExchangeMatcher implements ServerWe private final PathPattern pattern; - private final HttpMethod method; + private final @Nullable HttpMethod method; public PathPatternParserServerWebExchangeMatcher(PathPattern pattern) { this(pattern, null); } - public PathPatternParserServerWebExchangeMatcher(PathPattern pattern, HttpMethod method) { + public PathPatternParserServerWebExchangeMatcher(PathPattern pattern, @Nullable HttpMethod method) { Assert.notNull(pattern, "pattern cannot be null"); this.pattern = pattern; this.method = method; } - public PathPatternParserServerWebExchangeMatcher(String pattern, HttpMethod method) { + public PathPatternParserServerWebExchangeMatcher(String pattern, @Nullable HttpMethod method) { Assert.notNull(pattern, "pattern cannot be null"); this.pattern = parse(pattern); this.method = method; @@ -84,8 +85,9 @@ public final class PathPatternParserServerWebExchangeMatcher implements ServerWe } }); } - boolean match = this.pattern.matches(path); - if (!match) { + + PathPattern.PathMatchInfo pathMatchInfo = this.pattern.matchAndExtract(path); + if (pathMatchInfo == null) { return MatchResult.notMatch().doOnNext((result) -> { if (logger.isDebugEnabled()) { logger.debug("Request '" + request.getMethod() + " " + path + "' doesn't match '" + this.method @@ -93,7 +95,7 @@ public final class PathPatternParserServerWebExchangeMatcher implements ServerWe } }); } - Map pathVariables = this.pattern.matchAndExtract(path).getUriVariables(); + Map pathVariables = pathMatchInfo.getUriVariables(); Map variables = new HashMap<>(pathVariables); if (logger.isDebugEnabled()) { logger diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java index 92b1949dbc..ed438ee6c2 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/ServerWebExchangeMatchers.java @@ -19,6 +19,7 @@ package org.springframework.security.web.server.util.matcher; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.http.HttpMethod; @@ -43,7 +44,7 @@ public abstract class ServerWebExchangeMatchers { * @param patterns the patterns to match on * @return the matcher to use */ - public static ServerWebExchangeMatcher pathMatchers(HttpMethod method, String... patterns) { + public static ServerWebExchangeMatcher pathMatchers(@Nullable HttpMethod method, String... patterns) { List matchers = new ArrayList<>(patterns.length); for (String pattern : patterns) { matchers.add(new PathPatternParserServerWebExchangeMatcher(pattern, method)); @@ -76,7 +77,7 @@ public abstract class ServerWebExchangeMatchers { * @param pathPatterns the {@link PathPattern}s to match on * @return the matcher to use */ - public static ServerWebExchangeMatcher pathMatchers(HttpMethod method, PathPattern... pathPatterns) { + public static ServerWebExchangeMatcher pathMatchers(@Nullable HttpMethod method, PathPattern... pathPatterns) { List matchers = new ArrayList<>(pathPatterns.length); for (PathPattern pathPattern : pathPatterns) { matchers.add(new PathPatternParserServerWebExchangeMatcher(pathPattern, method)); diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/package-info.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/package-info.java new file mode 100644 index 0000000000..0ce81eb0a3 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/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. + */ + +/** + * Reactive APIs for matching requests which are used for, among other things, mapping + * authorization rules. + */ +@NullMarked +package org.springframework.security.web.server.util.matcher; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.java b/web/src/main/java/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.java index a4a3d155a3..16eb547a3e 100644 --- a/web/src/main/java/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.java +++ b/web/src/main/java/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.servlet.support.RequestDataValueProcessor; @@ -55,7 +56,7 @@ public final class CsrfRequestDataValueProcessor implements RequestDataValueProc } @Override - public String processFormFieldValue(HttpServletRequest request, String name, String value, String type) { + public String processFormFieldValue(HttpServletRequest request, @Nullable String name, String value, String type) { return value; } diff --git a/web/src/main/java/org/springframework/security/web/servlet/support/csrf/package-info.java b/web/src/main/java/org/springframework/security/web/servlet/support/csrf/package-info.java new file mode 100644 index 0000000000..18432f1265 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/servlet/support/csrf/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. + */ + +/** + * CSRF support classes for Spring's web MVC framework. Provides easy evaluation of the + * request context in views, and miscellaneous HandlerInterceptor implementations. + */ +@NullMarked +package org.springframework.security.web.servlet.support.csrf; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/servlet/util/matcher/package-info.java b/web/src/main/java/org/springframework/security/web/servlet/util/matcher/package-info.java new file mode 100644 index 0000000000..5fdff716e3 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/servlet/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. + */ + +/** + * Integration with Spring Framework's support for matching HTTP request paths. + */ +@NullMarked +package org.springframework.security.web.servlet.util.matcher; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java index 021f3bcc27..c27e7984ac 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java @@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationDetailsSource; @@ -87,11 +88,11 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private AuthenticationEntryPoint authenticationEntryPoint; + private @Nullable AuthenticationEntryPoint authenticationEntryPoint; - private AuthenticationManager authenticationManager; + private @Nullable AuthenticationManager authenticationManager; - private List logoutHandlers; + private @Nullable List logoutHandlers; private SecurityContextRepository securityContextRepository; @@ -117,7 +118,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { * is not authenticated. */ - void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + void setAuthenticationEntryPoint(@Nullable AuthenticationEntryPoint authenticationEntryPoint) { this.authenticationEntryPoint = authenticationEntryPoint; } @@ -135,7 +136,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { * @param authenticationManager the {@link AuthenticationManager} to use when invoking * {@link HttpServletRequest#login(String, String)} */ - void setAuthenticationManager(AuthenticationManager authenticationManager) { + void setAuthenticationManager(@Nullable AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @@ -154,7 +155,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { * @param logoutHandlers the {@code List}s when invoking * {@link HttpServletRequest#logout()}. */ - void setLogoutHandlers(List logoutHandlers) { + void setLogoutHandlers(@Nullable List logoutHandlers) { this.logoutHandlers = logoutHandlers; } @@ -193,7 +194,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { } @Override - public AsyncContext getAsyncContext() { + public @Nullable AsyncContext getAsyncContext() { AsyncContext asyncContext = super.getAsyncContext(); if (asyncContext == null) { return null; diff --git a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java b/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java index 99400d66c6..72d9b7bf71 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java @@ -26,6 +26,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -76,13 +77,14 @@ public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean { private String rolePrefix = "ROLE_"; + @SuppressWarnings("NullAway.Init") private HttpServletRequestFactory requestFactory; - private AuthenticationEntryPoint authenticationEntryPoint; + private @Nullable AuthenticationEntryPoint authenticationEntryPoint; - private AuthenticationManager authenticationManager; + private @Nullable AuthenticationManager authenticationManager; - private List logoutHandlers; + private @Nullable List logoutHandlers; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); diff --git a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapper.java b/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapper.java index 532e401dd2..c3053092e7 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapper.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapper.java @@ -21,6 +21,7 @@ import java.util.Collection; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -91,7 +92,7 @@ public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequest * Obtain the current active Authentication * @return the authentication object or null */ - private Authentication getAuthentication() { + private @Nullable Authentication getAuthentication() { Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); return (this.trustResolver.isAuthenticated(auth)) ? auth : null; } @@ -103,7 +104,7 @@ public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequest * @return the username or null if unavailable */ @Override - public String getRemoteUser() { + public @Nullable String getRemoteUser() { Authentication auth = getAuthentication(); if ((auth == null) || (auth.getPrincipal() == null)) { return null; @@ -123,7 +124,7 @@ public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequest * @return the Authentication, or null */ @Override - public Principal getUserPrincipal() { + public @Nullable Principal getUserPrincipal() { Authentication auth = getAuthentication(); if ((auth == null) || (auth.getPrincipal() == null)) { return null; diff --git a/web/src/main/java/org/springframework/security/web/servletapi/package-info.java b/web/src/main/java/org/springframework/security/web/servletapi/package-info.java index 74fccf2410..40e54e1f1e 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/package-info.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/package-info.java @@ -21,4 +21,7 @@ * To use, simply add the {@code SecurityContextHolderAwareRequestFilter} to the Spring * Security filter chain. */ +@NullMarked package org.springframework.security.web.servletapi; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java index 0af21c8b51..6a6d638833 100644 --- a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java @@ -26,6 +26,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.core.Authentication; @@ -33,6 +34,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; @@ -73,9 +75,9 @@ public class ConcurrentSessionFilter extends GenericFilterBean { private final SessionRegistry sessionRegistry; - private String expiredUrl; + private @Nullable String expiredUrl; - private RedirectStrategy redirectStrategy; + private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); private LogoutHandler handlers = new CompositeLogoutHandler(new SecurityContextLogoutHandler()); @@ -162,6 +164,7 @@ public class ConcurrentSessionFilter extends GenericFilterBean { */ @Deprecated protected String determineExpiredUrl(HttpServletRequest request, SessionInformation info) { + Assert.notNull(this.expiredUrl, "expiredUrl cannot be null"); return this.expiredUrl; } @@ -205,6 +208,7 @@ public class ConcurrentSessionFilter extends GenericFilterBean { */ @Deprecated public void setRedirectStrategy(RedirectStrategy redirectStrategy) { + Assert.notNull(redirectStrategy, "redirectStrategy cannot be null"); this.redirectStrategy = redirectStrategy; } diff --git a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java index e21e2403aa..fd474654df 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java @@ -19,6 +19,7 @@ package org.springframework.security.web.session; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEvent; import org.springframework.security.core.session.SessionInformation; @@ -37,7 +38,7 @@ public final class SessionInformationExpiredEvent extends ApplicationEvent { private final HttpServletResponse response; - private final FilterChain filterChain; + private final @Nullable FilterChain filterChain; /** * Creates a new instance @@ -59,7 +60,7 @@ public final class SessionInformationExpiredEvent extends ApplicationEvent { * @since 6.4 */ public SessionInformationExpiredEvent(SessionInformation sessionInformation, HttpServletRequest request, - HttpServletResponse response, FilterChain filterChain) { + HttpServletResponse response, @Nullable FilterChain filterChain) { super(sessionInformation); Assert.notNull(request, "request cannot be null"); Assert.notNull(response, "response cannot be null"); @@ -90,7 +91,7 @@ public final class SessionInformationExpiredEvent extends ApplicationEvent { * @return the filter chain. Can be {@code null}. * @since 6.4 */ - public FilterChain getFilterChain() { + public @Nullable FilterChain getFilterChain() { return this.filterChain; } diff --git a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java index 44bf9139b9..6b671ebdc3 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java @@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -63,7 +64,7 @@ public class SessionManagementFilter extends GenericFilterBean { private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - private InvalidSessionStrategy invalidSessionStrategy = null; + private @Nullable InvalidSessionStrategy invalidSessionStrategy = null; private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); diff --git a/web/src/main/java/org/springframework/security/web/session/package-info.java b/web/src/main/java/org/springframework/security/web/session/package-info.java index 7519d7d718..365ddab4ef 100644 --- a/web/src/main/java/org/springframework/security/web/session/package-info.java +++ b/web/src/main/java/org/springframework/security/web/session/package-info.java @@ -17,4 +17,7 @@ /** * Session management filters, {@code HttpSession} events and publisher classes. */ +@NullMarked package org.springframework.security.web.session; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/transport/package-info.java b/web/src/main/java/org/springframework/security/web/transport/package-info.java new file mode 100644 index 0000000000..7e0fdbe63b --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/transport/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. + */ + +/** + * Spring Security HTTP transport support. + */ +@NullMarked +package org.springframework.security.web.transport; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/util/RedirectUrlBuilder.java b/web/src/main/java/org/springframework/security/web/util/RedirectUrlBuilder.java index 5bc54d18ab..486f95b8bf 100644 --- a/web/src/main/java/org/springframework/security/web/util/RedirectUrlBuilder.java +++ b/web/src/main/java/org/springframework/security/web/util/RedirectUrlBuilder.java @@ -16,6 +16,8 @@ package org.springframework.security.web.util; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -28,19 +30,19 @@ import org.springframework.util.Assert; */ public class RedirectUrlBuilder { - private String scheme; + private @Nullable String scheme; - private String serverName; + private @Nullable String serverName; private int port; - private String contextPath; + private @Nullable String contextPath; - private String servletPath; + private @Nullable String servletPath; - private String pathInfo; + private @Nullable String pathInfo; - private String query; + private @Nullable String query; public void setScheme(String scheme) { Assert.isTrue("http".equals(scheme) || "https".equals(scheme), () -> "Unsupported scheme '" + scheme + "'"); diff --git a/web/src/main/java/org/springframework/security/web/util/ThrowableAnalyzer.java b/web/src/main/java/org/springframework/security/web/util/ThrowableAnalyzer.java index 24c7166f6a..48a5a79fdb 100755 --- a/web/src/main/java/org/springframework/security/web/util/ThrowableAnalyzer.java +++ b/web/src/main/java/org/springframework/security/web/util/ThrowableAnalyzer.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -166,7 +168,7 @@ public class ThrowableAnalyzer { * @param throwable the Throwable (not null * @return the cause, may be null if none could be resolved */ - private Throwable extractCause(Throwable throwable) { + private @Nullable Throwable extractCause(Throwable throwable) { for (Map.Entry, ThrowableCauseExtractor> entry : this.extractorMap.entrySet()) { Class throwableType = entry.getKey(); if (throwableType.isInstance(throwable)) { @@ -188,7 +190,8 @@ public class ThrowableAnalyzer { * @throws IllegalArgumentException if the provided type is null or no * subclass of Throwable */ - public final Throwable getFirstThrowableOfType(Class throwableType, Throwable[] chain) { + public final @Nullable Throwable getFirstThrowableOfType(Class throwableType, + Throwable[] chain) { if (chain != null) { for (Throwable t : chain) { if ((t != null) && throwableType.isInstance(t)) { diff --git a/web/src/main/java/org/springframework/security/web/util/ThrowableCauseExtractor.java b/web/src/main/java/org/springframework/security/web/util/ThrowableCauseExtractor.java index 347e62a840..24baa45f88 100755 --- a/web/src/main/java/org/springframework/security/web/util/ThrowableCauseExtractor.java +++ b/web/src/main/java/org/springframework/security/web/util/ThrowableCauseExtractor.java @@ -16,6 +16,8 @@ package org.springframework.security.web.util; +import org.jspecify.annotations.Nullable; + /** * Interface for handlers extracting the cause out of a specific {@link Throwable} type. * @@ -32,6 +34,6 @@ public interface ThrowableCauseExtractor { * @throws IllegalArgumentException if throwable is null or * otherwise considered invalid for the implementation */ - Throwable extractCause(Throwable throwable); + @Nullable Throwable extractCause(Throwable throwable); } diff --git a/web/src/main/java/org/springframework/security/web/util/UrlUtils.java b/web/src/main/java/org/springframework/security/web/util/UrlUtils.java index 3098b69794..099caea7a5 100644 --- a/web/src/main/java/org/springframework/security/web/util/UrlUtils.java +++ b/web/src/main/java/org/springframework/security/web/util/UrlUtils.java @@ -20,6 +20,7 @@ import java.util.Locale; import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; /** * Provides static methods for composing URLs. @@ -49,7 +50,7 @@ public final class UrlUtils { * @return the full URL, suitable for redirects (not decoded). */ public static String buildFullRequestUrl(String scheme, String serverName, int serverPort, String requestURI, - String queryString) { + @Nullable String queryString) { scheme = scheme.toLowerCase(Locale.ENGLISH); StringBuilder url = new StringBuilder(); url.append(scheme).append("://").append(serverName); diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/ELRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/ELRequestMatcher.java index 52bd398d30..5ab29c7b8a 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/ELRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/ELRequestMatcher.java @@ -23,6 +23,7 @@ import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint; +import org.springframework.util.Assert; /** * A RequestMatcher implementation which uses a SpEL expression @@ -51,7 +52,10 @@ public class ELRequestMatcher implements RequestMatcher { @Override public boolean matches(HttpServletRequest request) { EvaluationContext context = createELContext(request); - return this.expression.getValue(context, Boolean.class); + Boolean result = this.expression.getValue(context, Boolean.class); + Assert.notNull(result, + "The expression " + this.expression.getExpressionString() + " returned null. Expected true|false"); + return result; } /** diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java index 65de0d8c8a..6f9f9e642f 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; 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.http.HttpMethod; @@ -51,7 +52,7 @@ public final class RegexRequestMatcher implements RequestMatcher { private final Pattern pattern; - private final HttpMethod httpMethod; + private final @Nullable HttpMethod httpMethod; /** * Creates a case-sensitive {@code Pattern} instance to match against the request. @@ -90,7 +91,7 @@ public final class RegexRequestMatcher implements RequestMatcher { * @param pattern the regular expression to compile into a pattern. * @param httpMethod the HTTP method to match. May be null to match all methods. */ - public RegexRequestMatcher(String pattern, String httpMethod) { + public RegexRequestMatcher(String pattern, @Nullable String httpMethod) { this(pattern, httpMethod, false); } @@ -101,7 +102,7 @@ public final class RegexRequestMatcher implements RequestMatcher { * @param caseInsensitive if true, the pattern will be compiled with the * {@link Pattern#CASE_INSENSITIVE} flag set. */ - public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsensitive) { + public RegexRequestMatcher(String pattern, @Nullable String httpMethod, boolean caseInsensitive) { this.pattern = Pattern.compile(pattern, caseInsensitive ? CASE_INSENSITIVE : DEFAULT); this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null; } diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/RequestHeaderRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/RequestHeaderRequestMatcher.java index 6ba72f20ec..e1571f625c 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/RequestHeaderRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/RequestHeaderRequestMatcher.java @@ -17,6 +17,7 @@ package org.springframework.security.web.util.matcher; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -54,7 +55,7 @@ public final class RequestHeaderRequestMatcher implements RequestMatcher { private final String expectedHeaderName; - private final String expectedHeaderValue; + private final @Nullable String expectedHeaderValue; /** * Creates a new instance that will match if a header by the name of @@ -75,7 +76,7 @@ public final class RequestHeaderRequestMatcher implements RequestMatcher { * @param expectedHeaderValue the expected header value or null if the value does not * matter */ - public RequestHeaderRequestMatcher(String expectedHeaderName, String expectedHeaderValue) { + public RequestHeaderRequestMatcher(String expectedHeaderName, @Nullable String expectedHeaderValue) { Assert.notNull(expectedHeaderName, "headerName cannot be null"); this.expectedHeaderName = expectedHeaderName; this.expectedHeaderValue = expectedHeaderValue; diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/package-info.java b/web/src/main/java/org/springframework/security/web/util/matcher/package-info.java new file mode 100644 index 0000000000..5524ca0bcf --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/util/matcher/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. + */ + +/** + * Servlet APIs for matching requests which are used for, among other things, mapping + * authorization rules. + */ +@NullMarked +package org.springframework.security.web.util.matcher; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/main/java/org/springframework/security/web/util/package-info.java b/web/src/main/java/org/springframework/security/web/util/package-info.java index ed69c4ed48..bb20610326 100644 --- a/web/src/main/java/org/springframework/security/web/util/package-info.java +++ b/web/src/main/java/org/springframework/security/web/util/package-info.java @@ -19,4 +19,7 @@ *

    * Should not depend on any other framework classes. */ +@NullMarked package org.springframework.security.web.util; + +import org.jspecify.annotations.NullMarked; diff --git a/web/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java b/web/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java index 65ea3d9f4f..dd746f35a6 100644 --- a/web/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java +++ b/web/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java @@ -109,7 +109,7 @@ public class DefaultFilterInvocationSecurityMetadataSourceTests { createFids("/somepage**", HttpMethod.GET); FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST"); Collection attrs = this.fids.getAttributes(fi); - assertThat(attrs).isNull(); + assertThat(attrs).isEmpty(); } // SEC-1236 diff --git a/web/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java b/web/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java index da0e561799..6790aedff4 100644 --- a/web/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java +++ b/web/src/test/java/org/springframework/security/web/server/util/matcher/PathMatcherServerWebExchangeMatcherTests.java @@ -80,7 +80,6 @@ public class PathMatcherServerWebExchangeMatcherTests { @Test public void matchesWhenPathMatcherTrueThenReturnTrue() { - given(this.pattern.matches(any())).willReturn(true); given(this.pattern.matchAndExtract(any())).willReturn(this.pathMatchInfo); given(this.pathMatchInfo.getUriVariables()).willReturn(new HashMap<>()); assertThat(this.matcher.matches(this.exchange).block().isMatch()).isTrue(); @@ -88,7 +87,7 @@ public class PathMatcherServerWebExchangeMatcherTests { @Test public void matchesWhenPathMatcherFalseThenReturnFalse() { - given(this.pattern.matches(any())).willReturn(false); + given(this.pattern.matchAndExtract(any())).willReturn(null); assertThat(this.matcher.matches(this.exchange).block().isMatch()).isFalse(); } @@ -96,7 +95,6 @@ public class PathMatcherServerWebExchangeMatcherTests { public void matchesWhenPathMatcherTrueAndMethodTrueThenReturnTrue() { this.matcher = new PathPatternParserServerWebExchangeMatcher(this.pattern, this.exchange.getRequest().getMethod()); - given(this.pattern.matches(any())).willReturn(true); given(this.pattern.matchAndExtract(any())).willReturn(this.pathMatchInfo); given(this.pathMatchInfo.getUriVariables()).willReturn(new HashMap<>()); assertThat(this.matcher.matches(this.exchange).block().isMatch()).isTrue();